mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-12 01:30:49 +01:00
Merge pull request #1584 from bnnm/api-misc8
- Add .rvw extension [Half-Minute Hero (PC)] - Fix some .psb [The Quintessential Quintuplets: Memories of a Quintessential Summer (Switch)] - cleanup
This commit is contained in:
commit
202840b477
@ -25,8 +25,8 @@ static FILE* get_output_file(const char* filename) {
|
||||
return outfile;
|
||||
}
|
||||
|
||||
static libvgmstream_streamfile_t* get_streamfile(const char* filename) {
|
||||
return libvgmstream_streamfile_open_from_stdio(filename);
|
||||
static libstreamfile_t* get_streamfile(const char* filename) {
|
||||
return libstreamfile_open_from_stdio(filename);
|
||||
}
|
||||
|
||||
static int api_example(const char* infile) {
|
||||
@ -61,7 +61,7 @@ static int api_example(const char* infile) {
|
||||
};
|
||||
err = libvgmstream_open_song(lib, &opt);
|
||||
// external SF is not needed after _open
|
||||
libvgmstream_streamfile_close(opt.libsf);
|
||||
libstreamfile_close(opt.libsf);
|
||||
|
||||
if (err < 0) {
|
||||
printf("not a valid file\n");
|
||||
@ -199,22 +199,22 @@ static void test_lib_extensions() {
|
||||
assert(exts == NULL);
|
||||
}
|
||||
|
||||
static libvgmstream_streamfile_t* test_libsf_open() {
|
||||
static libstreamfile_t* test_libsf_open() {
|
||||
VGM_STEP();
|
||||
|
||||
libvgmstream_streamfile_t* libsf = NULL;
|
||||
libstreamfile_t* libsf = NULL;
|
||||
|
||||
libsf = libvgmstream_streamfile_open_from_stdio("api.bin_wrong");
|
||||
libsf = libstreamfile_open_from_stdio("api.bin_wrong");
|
||||
assert(libsf == NULL);
|
||||
|
||||
libsf = libvgmstream_streamfile_open_from_stdio("api.bin");
|
||||
libsf = libstreamfile_open_from_stdio("api.bin");
|
||||
assert(libsf != NULL);
|
||||
|
||||
return libsf;
|
||||
}
|
||||
|
||||
|
||||
static void test_libsf_read(libvgmstream_streamfile_t* libsf) {
|
||||
static void test_libsf_read(libstreamfile_t* libsf) {
|
||||
VGM_STEP();
|
||||
|
||||
int read;
|
||||
@ -233,7 +233,7 @@ static void test_libsf_read(libvgmstream_streamfile_t* libsf) {
|
||||
}
|
||||
}
|
||||
|
||||
static void test_libsf_seek_read(libvgmstream_streamfile_t* libsf) {
|
||||
static void test_libsf_seek_read(libstreamfile_t* libsf) {
|
||||
VGM_STEP();
|
||||
|
||||
int read, res;
|
||||
@ -255,27 +255,27 @@ static void test_libsf_seek_read(libvgmstream_streamfile_t* libsf) {
|
||||
assert(read == 0);
|
||||
}
|
||||
|
||||
static void test_libsf_size(libvgmstream_streamfile_t* libsf) {
|
||||
static void test_libsf_size(libstreamfile_t* libsf) {
|
||||
VGM_STEP();
|
||||
|
||||
int64_t size = libsf->get_size(libsf->user_data);
|
||||
assert(size == 0x20000);
|
||||
}
|
||||
|
||||
static void test_libsf_name(libvgmstream_streamfile_t* libsf) {
|
||||
static void test_libsf_name(libstreamfile_t* libsf) {
|
||||
VGM_STEP();
|
||||
|
||||
const char* name = libsf->get_name(libsf->user_data);
|
||||
assert(strcmp(name, "api.bin") == 0);
|
||||
}
|
||||
|
||||
static void test_libsf_reopen(libvgmstream_streamfile_t* libsf) {
|
||||
static void test_libsf_reopen(libstreamfile_t* libsf) {
|
||||
VGM_STEP();
|
||||
|
||||
uint8_t buf[0x20];
|
||||
int read;
|
||||
|
||||
libvgmstream_streamfile_t* newsf = NULL;
|
||||
libstreamfile_t* newsf = NULL;
|
||||
|
||||
newsf = libsf->open(libsf->user_data, "api2.bin_wrong");
|
||||
assert(newsf == NULL);
|
||||
@ -290,7 +290,7 @@ static void test_libsf_reopen(libvgmstream_streamfile_t* libsf) {
|
||||
newsf->close(newsf);
|
||||
}
|
||||
|
||||
static void test_libsf_apisf(libvgmstream_streamfile_t* libsf) {
|
||||
static void test_libsf_apisf(libstreamfile_t* libsf) {
|
||||
VGM_STEP();
|
||||
|
||||
STREAMFILE* sf = open_api_streamfile(libsf);
|
||||
@ -316,7 +316,7 @@ static void test_libsf_apisf(libvgmstream_streamfile_t* libsf) {
|
||||
static void test_lib_streamfile() {
|
||||
VGM_STEP();
|
||||
|
||||
libvgmstream_streamfile_t* libsf = test_libsf_open();
|
||||
libstreamfile_t* libsf = test_libsf_open();
|
||||
test_libsf_read(libsf);
|
||||
test_libsf_seek_read(libsf);
|
||||
test_libsf_size(libsf);
|
||||
@ -331,11 +331,11 @@ static void test_lib_streamfile() {
|
||||
static void test_lib_tags() {
|
||||
VGM_STEP();
|
||||
|
||||
libvgmstream_streamfile_t* libsf = NULL;
|
||||
libstreamfile_t* libsf = NULL;
|
||||
libvgmstream_tags_t* tags = NULL;
|
||||
bool more = false;
|
||||
|
||||
libsf = libvgmstream_streamfile_open_from_stdio("sample_!tags.m3u");
|
||||
libsf = libstreamfile_open_from_stdio("sample_!tags.m3u");
|
||||
assert(libsf != NULL);
|
||||
|
||||
tags = libvgmstream_tags_init(libsf);
|
||||
@ -374,7 +374,7 @@ static void test_lib_tags() {
|
||||
assert(!more);
|
||||
|
||||
libvgmstream_tags_free(tags);
|
||||
libvgmstream_streamfile_close(libsf);
|
||||
libstreamfile_close(libsf);
|
||||
}
|
||||
|
||||
|
||||
|
@ -79,7 +79,7 @@ size_t wav_make_header(uint8_t* buf, size_t buf_size, wav_header_t* wav) {
|
||||
|
||||
if (!wav->sample_size)
|
||||
wav->sample_size = sizeof(short);
|
||||
if (wav->sample_size <= 0 || wav->sample_size >= 4)
|
||||
if (wav->sample_size <= 0x00 || wav->sample_size > 0x04)
|
||||
return 0;
|
||||
|
||||
size_t data_size = wav->sample_count * wav->channels * wav->sample_size;
|
||||
|
28
doc/DEV.md
28
doc/DEV.md
@ -67,7 +67,7 @@ Code is reasonably secure: some parts like IO are designed in a way should avoid
|
||||
|
||||
Some of the code can be inefficient or duplicated at places, but it isn't that much of a problem if gives clarity. vgmstream's performance is fast enough (as it mainly deals with playing songs in real time) so that favors clarity over optimization. Performance bottlenecks are mainly:
|
||||
- I/O: since I/O is buffered it's possible to needlessly trash the buffers when reading previous/next offsets back and forth. It's better to read linearly using big enough data chunks and cache values.
|
||||
- for loops: since your average audio file contains millions of samples, this means lots of loops. Care should be taken to avoid unnecessary function calls or recalculations per single sample when multiple samples could be processed at once.
|
||||
- `for` loops: since your average audio file contains millions of samples, this means lots of loops. Care should be taken to avoid unnecessary function calls or recalculations per single sample when multiple samples could be processed at once.
|
||||
|
||||
|
||||
## Source structure
|
||||
@ -83,6 +83,7 @@ Some of the code can be inefficient or duplicated at places, but it isn't that m
|
||||
./src/ initial vgmstream code
|
||||
./src/base/ core vgmstream features
|
||||
./src/coding/ format data decoders
|
||||
./src/coding/lib/ lib-like decoders, external to vgmstream
|
||||
./src/layout/ format data demuxers
|
||||
./src/meta/ format header parsers
|
||||
./src/util/ helpers
|
||||
@ -101,7 +102,7 @@ Quick list of some audio terms used through vgmstream, applied to code. Mainly m
|
||||
- audio sample: digital audio unit (single value) to define playable sound. A sound is a wave, and an array of many samples (digital) together make a wave (analog).
|
||||
- Each output channel has its own set of samples.
|
||||
- Normally `1 sample` actually means `1 sample for every channel` (common standard that makes code logic simpler).
|
||||
- If an stereo file has `1000000` samples it actually means `2*1000000` total samples.
|
||||
- If an stereo file has `1000000` samples it actually means `2 channels * 1000000` total samples.
|
||||
- sample rate: number of samples per second (in *hz*). Also called frequency.
|
||||
- If a file has a sample rate *44100hz* and lasts *30 seconds* this means `44100 * 30 = 1323000` samples.
|
||||
- Since many samples together make a wave, the higher the sample rate the more samples we have, and the better-sounding wave we get.
|
||||
@ -112,8 +113,8 @@ Quick list of some audio terms used through vgmstream, applied to code. Mainly m
|
||||
- block: a generic section of data, made of one or many frames for all channels.
|
||||
|
||||
|
||||
## Overview
|
||||
vgmstream works by parsing a music stream header (*meta/*), preparing/controlling data and sample buffers (*layout/*) and decoding the compressed data into listenable PCM samples (*coding/*).
|
||||
## Process overview
|
||||
vgmstream works by parsing audio header metadata (*meta/*), preparing + managing data and sample buffers (*layout/*) and decoding the compressed data into listenable PCM samples (*coding/*).
|
||||
|
||||
Very simplified it goes like this:
|
||||
- player (CLI, plugin, etc) opens a file stream (STREAMFILE) *[plugin's main/decode]*
|
||||
@ -127,14 +128,11 @@ Very simplified it goes like this:
|
||||
- layout moves offsets back to loop_start when loop_end is reached *[decode_do_loop]*
|
||||
- player closes the VGMSTREAM once the stream is finished
|
||||
|
||||
vgsmtream's main code (located in src) may be considered "libvgmstream", and plugins interface it through vgmstream.h, mainly the part commented as "vgmstream public API". There isn't a clean external API at the moment, this may be improved later.
|
||||
|
||||
## Components
|
||||
## Internal parts
|
||||
|
||||
### STREAMFILEs
|
||||
Structs with I/O callbacks that vgmstream uses in place of stdio/FILEs. All I/O must be done through STREAMFILEs as it lets plugins set up their own. This includes reading data or opening other STREAMFILEs (ex. when a header has companion files that need to be parsed, or during setup).
|
||||
|
||||
Players should open a base STREAMFILE and pass it to init_vgmstream. Once it's done this STREAMFILE must be closed, as internally vgmstream opens its own copy (using the base one's callbacks).
|
||||
Structs with I/O callbacks that vgmstream uses in place of stdio/FILEs. All I/O must be done through STREAMFILEs as it lets plugins set up their own I/O. This includes reading data or opening other STREAMFILEs (ex. when a header has companion files that need to be parsed, or during setup).
|
||||
|
||||
For optimization purposes vgmstream may open a copy of the FILE per channel, as each has its own I/O buffer, and channel data can be too separate to fit a single buffer.
|
||||
|
||||
@ -148,18 +146,18 @@ Certain metas combine those streamfiles together with special layouts to support
|
||||
|
||||
|
||||
### VGMSTREAM
|
||||
The VGMSTREAM (caps) is the main struct created during init when a file is successfully recognized and parsed. It holds the file's configuration (channels, sample rate, decoder, layout, samples, loop points, etc) and decoder state (STREAMFILEs, offsets per channel, current sample, etc), and is used to interact with the API.
|
||||
The VGMSTREAM (caps) is the main struct created during init when a file is successfully recognized and parsed. It holds the file's configuration (channels, sample rate, decoder, layout, samples, loop points, etc) and decoder state (STREAMFILEs, offsets per channel, current sample, etc).
|
||||
|
||||
### metas
|
||||
Metadata (header) parsers that identify and handle formats.
|
||||
|
||||
To add a new one:
|
||||
- *src/meta/(format-name).c*: create new init_vgmstream_(format-name) parser that tests the extension and header id, reads all needed info from the stream header and sets up the VGMSTREAM
|
||||
- *src/meta/(format-name).c*: create new `init_vgmstream_(format-name)` parser that tests the extension and header id, reads all needed info from the stream header and sets up the VGMSTREAM
|
||||
- *src/meta/meta.h*: define parser's init
|
||||
- *src/vgmstream.h*: define meta type in the meta_t list
|
||||
- *src/vgmstream.c*: add parser init to the init list
|
||||
- *src/formats.c*: add new extension to the format list, add meta type description
|
||||
- *src/libvgmstream.vcproj/vcxproj/filters*: add to compile new (format-name).c parser in VS
|
||||
- *src/vgmstream_types.h*: define meta type in the meta_t list
|
||||
- *src/vgmstream_init.c*: add parser init to the init list
|
||||
- *src/formats.c*: add new extension to the format list (if needed), add meta type description
|
||||
- *src/libvgmstream.vcproj/vcxproj/filters*: add to compile new (format-name).c parser in VS (may use `vspf.py` on root)
|
||||
- if the format needs an external library don't forget to mark optional parts with: *#ifdef VGM_USE_X ... #endif*
|
||||
|
||||
Ultimately the meta must alloc the VGMSTREAM, set config and initial state. vgmstream needs the total of number samples to work, so at times must convert from data sizes to samples (doing calculations or using helpers).
|
||||
|
@ -325,10 +325,6 @@ different internally (encrypted, different versions, etc) and not always can be
|
||||
- *xwb*: `.xwb .xna .hwb .bd .(extensionless) + .wbh`
|
||||
- Subfiles: *riff*
|
||||
- Codecs: PCM8_U PCM16LE PCM16BE XBOX_IMA MSADPCM XMA1 XMA2 FFmpeg(various) XWMA ATRAC3 OGG_VORBIS NGC_DSP
|
||||
- **ps2_xa30.c**
|
||||
- Reflections XA30 PS2 header [*PS2_XA30*]
|
||||
- *ps2_xa30*: `.xa .xa30`
|
||||
- Codecs: PSX
|
||||
- **musc.c**
|
||||
- Krome MUSC header [*MUSC*]
|
||||
- *musc*: `.mus .musc`
|
||||
@ -422,10 +418,6 @@ different internally (encrypted, different versions, etc) and not always can be
|
||||
- Falcom .DEC RIFF header [*DEC*]
|
||||
- *dec*: `.dec .de2 + .(external)`
|
||||
- Codecs: MSADPCM
|
||||
- **vs.c**
|
||||
- Melbourne House .VS header [*VS*]
|
||||
- *vs*: `.vs`
|
||||
- Codecs: PSX
|
||||
- **xmu.c**
|
||||
- Outrage XMU header [*XMU*]
|
||||
- *xmu*: `.xmu`
|
||||
@ -1047,7 +1039,7 @@ different internally (encrypted, different versions, etc) and not always can be
|
||||
- **xa_xa30.c**
|
||||
- Reflections XA30 header [*XA_XA30*]
|
||||
- *xa_xa30*: `.xa .xa30 .e4x`
|
||||
- Codecs: PCM16LE REF_IMA
|
||||
- Codecs: PCM16LE REF_IMA PSX
|
||||
- **xa_04sw.c**
|
||||
- Reflections 04SW header [*XA_04SW*]
|
||||
- *xa_04sw*: `.xa`
|
||||
@ -1878,6 +1870,10 @@ different internally (encrypted, different versions, etc) and not always can be
|
||||
- Konami .PCM header [*PCM_KCEJE*]
|
||||
- *pcm_kceje*: `.pcm`
|
||||
- Codecs: PCM16LE
|
||||
- **vs.c**
|
||||
- Melbourne House .VS header [*VS_MH*]
|
||||
- *vs_mh*: `.vs`
|
||||
- Codecs: PSX
|
||||
- **pos.c**
|
||||
- RIFF WAVE header (.pos looping) [*RIFF_WAVE_POS*]
|
||||
- *pos*: `.pos + .wav`
|
||||
@ -1892,7 +1888,7 @@ different internally (encrypted, different versions, etc) and not always can be
|
||||
- Codecs: NGC_DTK
|
||||
- **mpeg.c**
|
||||
- MPEG header [*MPEG*]
|
||||
- *mpeg*: `.mp3 .mp2 .lmp3 .lmp2 .mus .imf .aix .wav .lwav .(extensionless)`
|
||||
- *mpeg*: `.mp3 .mp2 .lmp3 .lmp2 .mus .imf .aix .wav .lwav .nfx .(extensionless)`
|
||||
- Codecs: MPEG
|
||||
- **btsnd.c**
|
||||
- Nintendo Wii U Menu Boot Sound header [*BTSND*]
|
||||
|
@ -578,6 +578,8 @@ This is particularly interesting when combined with offsets to some long value.
|
||||
|
||||
It also allows parsing formats that set offsets to another offset, by "chaining" `base_offset`. With `base_offset = @0x10` (pointing to `0x40`) then `base_offset = @0x20`, it reads value at `0x60`. Set to 0 when you want to disable/reset the chain: `base_offset = @0x10` then `base_offset = 0` then `base_offset = @0x20` reads value at `0x20`
|
||||
|
||||
You can also use `base_offset` to read values from the end of the stream: `base_offset = data_size - 0x100`.
|
||||
|
||||
```
|
||||
base_offset = (value)
|
||||
```
|
||||
|
@ -35,6 +35,12 @@ field.bfstm #C3,4
|
||||
# notice it has the original filename + extension, then commands, then .txtp
|
||||
```
|
||||
|
||||
**bgm01-loop-repeat.txtp**
|
||||
```
|
||||
bgm01.fsb #e
|
||||
```
|
||||
|
||||
|
||||
## TXTP MODES
|
||||
TXTP can join and play together multiple songs in various ways by setting a file list and mode:
|
||||
```
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "api_internal.h"
|
||||
#include "mixing.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
#define INTERNAL_BUF_SAMPLES 1024
|
||||
@ -81,4 +82,27 @@ void libvgmstream_priv_reset(libvgmstream_priv_t* priv, bool reset_buf) {
|
||||
priv->decode_done = false;
|
||||
}
|
||||
|
||||
libvgmstream_sample_t api_get_output_sample_type(libvgmstream_priv_t* priv) {
|
||||
VGMSTREAM* v = priv->vgmstream;
|
||||
sfmt_t format = mixing_get_output_sample_type(v);
|
||||
switch(format) {
|
||||
case SFMT_S16: return LIBVGMSTREAM_SAMPLE_PCM16;
|
||||
case SFMT_FLT: return LIBVGMSTREAM_SAMPLE_FLOAT;
|
||||
default:
|
||||
return 0x00; //???
|
||||
}
|
||||
}
|
||||
|
||||
int api_get_sample_size(libvgmstream_sample_t sample_type) {
|
||||
switch(sample_type) {
|
||||
case LIBVGMSTREAM_SAMPLE_PCM24:
|
||||
case LIBVGMSTREAM_SAMPLE_PCM32:
|
||||
case LIBVGMSTREAM_SAMPLE_FLOAT:
|
||||
return 0x04;
|
||||
case LIBVGMSTREAM_SAMPLE_PCM16:
|
||||
default:
|
||||
return 0x02;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,8 +1,8 @@
|
||||
#include "api_internal.h"
|
||||
#include "sbuf.h"
|
||||
#include "mixing.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
#define INTERNAL_BUF_SAMPLES 1024
|
||||
|
||||
|
||||
static void load_vgmstream(libvgmstream_priv_t* priv, libvgmstream_options_t* opt) {
|
||||
STREAMFILE* sf_api = open_api_streamfile(opt->libsf);
|
||||
@ -49,6 +49,11 @@ static void prepare_mixing(libvgmstream_priv_t* priv, libvgmstream_options_t* op
|
||||
vgmstream_mixing_stereo_only(priv->vgmstream, opt->stereo_track - 1);
|
||||
}
|
||||
|
||||
if (priv->cfg.force_pcm16)
|
||||
mixing_macro_output_sample_format(priv->vgmstream, SFMT_S16);
|
||||
else if (priv->cfg.force_float)
|
||||
mixing_macro_output_sample_format(priv->vgmstream, SFMT_FLT);
|
||||
|
||||
vgmstream_mixing_enable(priv->vgmstream, INTERNAL_BUF_SAMPLES, NULL /*&input_channels*/, NULL /*&output_channels*/);
|
||||
}
|
||||
|
||||
@ -65,10 +70,6 @@ static void update_format_info(libvgmstream_priv_t* priv) {
|
||||
libvgmstream_format_t* fmt = &priv->fmt;
|
||||
VGMSTREAM* v = priv->vgmstream;
|
||||
|
||||
fmt->sample_size = 0x02;
|
||||
fmt->sample_type = LIBVGMSTREAM_SAMPLE_PCM16;
|
||||
fmt->sample_rate = v->sample_rate;
|
||||
|
||||
fmt->subsong_index = v->stream_index;
|
||||
fmt->subsong_count = v->num_streams;
|
||||
|
||||
@ -77,6 +78,11 @@ static void update_format_info(libvgmstream_priv_t* priv) {
|
||||
vgmstream_mixing_enable(v, 0, &fmt->input_channels, &fmt->channels);
|
||||
fmt->channel_layout = v->channel_layout;
|
||||
|
||||
fmt->sample_type = api_get_output_sample_type(priv);
|
||||
fmt->sample_size = api_get_sample_size(fmt->sample_type);
|
||||
|
||||
fmt->sample_rate = v->sample_rate;
|
||||
|
||||
fmt->stream_samples = v->num_samples;
|
||||
fmt->loop_start = v->loop_start_sample;
|
||||
fmt->loop_end = v->loop_end_sample;
|
||||
|
@ -1,32 +1,45 @@
|
||||
#include "api_internal.h"
|
||||
#include "mixing.h"
|
||||
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
#define INTERNAL_BUF_SAMPLES 1024
|
||||
//TODO: use internal
|
||||
|
||||
static void reset_buf(libvgmstream_priv_t* priv) {
|
||||
/* state reset */
|
||||
static bool reset_buf(libvgmstream_priv_t* priv) {
|
||||
// state reset
|
||||
priv->buf.samples = 0;
|
||||
priv->buf.bytes = 0;
|
||||
priv->buf.consumed = 0;
|
||||
|
||||
if (priv->buf.initialized)
|
||||
return;
|
||||
int output_channels = priv->vgmstream->channels;
|
||||
int input_channels = priv->vgmstream->channels;
|
||||
return true;
|
||||
|
||||
// calc input/output values to reserve buf (should be as big as input or output)
|
||||
int input_channels = 0, output_channels = 0;
|
||||
vgmstream_mixing_enable(priv->vgmstream, 0, &input_channels, &output_channels); //query
|
||||
|
||||
/* static config */
|
||||
priv->buf.channels = input_channels;
|
||||
if (priv->buf.channels < output_channels)
|
||||
priv->buf.channels = output_channels;
|
||||
int min_channels = input_channels;
|
||||
if (min_channels < output_channels)
|
||||
min_channels = output_channels;
|
||||
|
||||
sfmt_t input_sfmt = mixing_get_input_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 output_sample_size = sfmt_get_sample_size(output_sfmt);
|
||||
|
||||
int min_sample_size = input_sample_size;
|
||||
if (min_sample_size < output_sample_size)
|
||||
min_sample_size = output_sample_size;
|
||||
|
||||
priv->buf.sample_size = sizeof(sample_t);
|
||||
priv->buf.max_samples = INTERNAL_BUF_SAMPLES;
|
||||
priv->buf.max_bytes = priv->buf.max_samples * priv->buf.sample_size * priv->buf.channels;
|
||||
priv->buf.data = malloc(priv->buf.max_bytes);
|
||||
priv->buf.sample_size = output_sample_size;
|
||||
priv->buf.channels = output_channels;
|
||||
|
||||
int max_bytes = priv->buf.max_samples * min_sample_size * min_channels;
|
||||
priv->buf.data = malloc(max_bytes);
|
||||
if (!priv->buf.data) return false;
|
||||
|
||||
priv->buf.initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void update_buf(libvgmstream_priv_t* priv, int samples_done) {
|
||||
@ -59,8 +72,7 @@ LIBVGMSTREAM_API int libvgmstream_render(libvgmstream_t* lib) {
|
||||
if (priv->decode_done)
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
reset_buf(priv);
|
||||
if (!priv->buf.data)
|
||||
if (!reset_buf(priv))
|
||||
return LIBVGMSTREAM_ERROR_GENERIC;
|
||||
|
||||
int to_get = priv->buf.max_samples;
|
||||
|
@ -11,6 +11,8 @@
|
||||
#define LIBVGMSTREAM_ERROR_GENERIC -1
|
||||
#define LIBVGMSTREAM_ERROR_DONE -2
|
||||
|
||||
#define INTERNAL_BUF_SAMPLES 1024
|
||||
|
||||
/* self-note: various API functions are just bridges to internal stuff.
|
||||
* Rather than changing the internal stuff to handle API structs/etc,
|
||||
* leave internals untouched for a while so external plugins/users may adapt.
|
||||
@ -20,11 +22,10 @@ typedef struct {
|
||||
bool initialized;
|
||||
void* data;
|
||||
|
||||
/* config */
|
||||
int channels;
|
||||
int max_bytes;
|
||||
/* config (output values channels/size after mixing, though buf may be as big as input size) */
|
||||
int max_samples;
|
||||
int sample_size;
|
||||
int channels; /* */
|
||||
int sample_size;
|
||||
|
||||
/* state */
|
||||
int samples;
|
||||
@ -56,8 +57,10 @@ typedef struct {
|
||||
|
||||
|
||||
void libvgmstream_priv_reset(libvgmstream_priv_t* priv, bool reset_buf);
|
||||
libvgmstream_sample_t api_get_output_sample_type(libvgmstream_priv_t* priv);
|
||||
int api_get_sample_size(libvgmstream_sample_t sample_type);
|
||||
|
||||
STREAMFILE* open_api_streamfile(libvgmstream_streamfile_t* libsf);
|
||||
STREAMFILE* open_api_streamfile(libstreamfile_t* libsf);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -1,9 +1,9 @@
|
||||
#include "api_internal.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
|
||||
static libvgmstream_streamfile_t* libvgmstream_streamfile_from_streamfile(STREAMFILE* sf);
|
||||
static libstreamfile_t* libstreamfile_from_streamfile(STREAMFILE* sf);
|
||||
|
||||
/* libvgmstream_streamfile_t for external use, as a default implementation calling some internal SF */
|
||||
/* libstreamfile_t for external use, as a default implementation calling some internal SF */
|
||||
|
||||
typedef struct {
|
||||
int64_t offset;
|
||||
@ -29,12 +29,12 @@ static int64_t libsf_seek(void* user_data, int64_t offset, int whence) {
|
||||
return -1;
|
||||
|
||||
switch (whence) {
|
||||
case LIBVGMSTREAM_STREAMFILE_SEEK_SET: /* absolute */
|
||||
case LIBSTREAMFILE_SEEK_SET: /* absolute */
|
||||
break;
|
||||
case LIBVGMSTREAM_STREAMFILE_SEEK_CUR: /* relative to current */
|
||||
case LIBSTREAMFILE_SEEK_CUR: /* relative to current */
|
||||
offset += data->offset;
|
||||
break;
|
||||
case LIBVGMSTREAM_STREAMFILE_SEEK_END: /* relative to file end (should be negative) */
|
||||
case LIBSTREAMFILE_SEEK_END: /* relative to file end (should be negative) */
|
||||
offset += data->size;
|
||||
break;
|
||||
default:
|
||||
@ -71,7 +71,7 @@ static const char* libsf_get_name(void* user_data) {
|
||||
return data->name;
|
||||
}
|
||||
|
||||
struct libvgmstream_streamfile_t* libsf_open(void* user_data, const char* filename) {
|
||||
struct libstreamfile_t* libsf_open(void* user_data, const char* filename) {
|
||||
libsf_data_t* data = user_data;
|
||||
if (!data || !data->inner_sf)
|
||||
return NULL;
|
||||
@ -80,7 +80,7 @@ struct libvgmstream_streamfile_t* libsf_open(void* user_data, const char* filena
|
||||
if (!sf)
|
||||
return NULL;
|
||||
|
||||
libvgmstream_streamfile_t* libsf = libvgmstream_streamfile_from_streamfile(sf);
|
||||
libstreamfile_t* libsf = libstreamfile_from_streamfile(sf);
|
||||
if (!libsf) {
|
||||
close_streamfile(sf);
|
||||
return NULL;
|
||||
@ -89,7 +89,7 @@ struct libvgmstream_streamfile_t* libsf_open(void* user_data, const char* filena
|
||||
return libsf;
|
||||
}
|
||||
|
||||
static void libsf_close(struct libvgmstream_streamfile_t* libsf) {
|
||||
static void libsf_close(struct libstreamfile_t* libsf) {
|
||||
if (!libsf)
|
||||
return;
|
||||
|
||||
@ -101,14 +101,14 @@ static void libsf_close(struct libvgmstream_streamfile_t* libsf) {
|
||||
free(libsf);
|
||||
}
|
||||
|
||||
static libvgmstream_streamfile_t* libvgmstream_streamfile_from_streamfile(STREAMFILE* sf) {
|
||||
static libstreamfile_t* libstreamfile_from_streamfile(STREAMFILE* sf) {
|
||||
if (!sf)
|
||||
return NULL;
|
||||
|
||||
libvgmstream_streamfile_t* libsf = NULL;
|
||||
libstreamfile_t* libsf = NULL;
|
||||
libsf_data_t* data = NULL;
|
||||
|
||||
libsf = calloc(1, sizeof(libvgmstream_streamfile_t));
|
||||
libsf = calloc(1, sizeof(libstreamfile_t));
|
||||
if (!libsf) goto fail;
|
||||
|
||||
libsf->read = libsf_read;
|
||||
@ -132,12 +132,12 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_open_from_stdio(const char* filename) {
|
||||
LIBVGMSTREAM_API libstreamfile_t* libstreamfile_open_from_stdio(const char* filename) {
|
||||
STREAMFILE* sf = open_stdio_streamfile(filename);
|
||||
if (!sf)
|
||||
return NULL;
|
||||
|
||||
libvgmstream_streamfile_t* libsf = libvgmstream_streamfile_from_streamfile(sf);
|
||||
libstreamfile_t* libsf = libstreamfile_from_streamfile(sf);
|
||||
if (!libsf) {
|
||||
close_streamfile(sf);
|
||||
return NULL;
|
||||
|
@ -3,11 +3,11 @@
|
||||
|
||||
typedef struct {
|
||||
VGMSTREAM_TAGS* vtags;
|
||||
libvgmstream_streamfile_t* libsf;
|
||||
libstreamfile_t* libsf;
|
||||
STREAMFILE* sf_tags;
|
||||
} libvgmstream_tags_priv_t;
|
||||
|
||||
LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libvgmstream_streamfile_t* libsf) {
|
||||
LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libstreamfile_t* libsf) {
|
||||
if (!libsf)
|
||||
return NULL;
|
||||
|
||||
|
@ -837,7 +837,7 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_filled, int samples_to_d
|
||||
|
||||
switch (vgmstream->coding_type) {
|
||||
case coding_SILENCE:
|
||||
sbuf_silence(buffer, samples_to_do, vgmstream->channels, 0);
|
||||
sbuf_silence_s16(buffer, samples_to_do, vgmstream->channels, 0);
|
||||
break;
|
||||
|
||||
case coding_CRI_ADX:
|
||||
|
@ -62,57 +62,66 @@ void mixer_update_channel(mixer_t* mixer) {
|
||||
|
||||
bool mixer_is_active(mixer_t* mixer) {
|
||||
/* no support or not need to apply */
|
||||
if (!mixer || !mixer->active || mixer->chain_count == 0)
|
||||
if (!mixer || !mixer->active)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
if (mixer->chain_count > 0)
|
||||
return true;
|
||||
|
||||
if (mixer->force_type != SFMT_NONE)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void mixer_process(mixer_t* mixer, sbuf_t* sbuf, int32_t current_pos) {
|
||||
|
||||
void mixer_process(mixer_t* mixer, sample_t* outbuf, int32_t sample_count, int32_t current_pos) {
|
||||
|
||||
/* no support or not need to apply */
|
||||
if (!mixer || !mixer->active || mixer->chain_count == 0)
|
||||
return;
|
||||
/* external */
|
||||
//if (!mixer_is_active(mixer))
|
||||
// return;
|
||||
|
||||
/* try to skip if no fades apply (set but does nothing yet) + only has fades
|
||||
* (could be done in mix op but avoids upgrading bufs in some cases) */
|
||||
mixer->current_subpos = 0;
|
||||
if (mixer->has_fade) {
|
||||
//;VGM_LOG("MIX: fade test %i, %i\n", data->has_non_fade, mixer_op_fade_is_active(data, current_pos, current_pos + sample_count));
|
||||
if (!mixer->has_non_fade && !mixer_op_fade_is_active(mixer, current_pos, current_pos + sample_count))
|
||||
if (!mixer->has_non_fade && !mixer_op_fade_is_active(mixer, current_pos, current_pos + sbuf->filled))
|
||||
return;
|
||||
|
||||
//;VGM_LOG("MIX: fade pos=%i\n", current_pos);
|
||||
mixer->current_subpos = current_pos;
|
||||
}
|
||||
|
||||
// upgrade buf for mixing (somehow using float buf rather than int32 is faster?)
|
||||
sbuf_copy_s16_to_f32(mixer->mixbuf, outbuf, sample_count, mixer->input_channels);
|
||||
// remix to temp buf for mixing (somehow using float buf rather than int32 is faster?)
|
||||
sbuf_copy_to_f32(mixer->mixbuf, sbuf);
|
||||
|
||||
/* apply mixing ops in order. Some ops change total channels they may change around:
|
||||
* - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2
|
||||
* - 2ch w/ "1u" = downmix to 1ch (current_channels decreases once)
|
||||
*/
|
||||
// apply mixing ops in order. current_channels 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/ "1u" = downmix to 1ch (current_channels decreases once)
|
||||
mixer->current_channels = mixer->input_channels;
|
||||
for (int m = 0; m < mixer->chain_count; m++) {
|
||||
mix_op_t* mix = &mixer->chain[m];
|
||||
|
||||
//TODO: set callback
|
||||
//TO-DO: set callback
|
||||
switch(mix->type) {
|
||||
case MIX_SWAP: mixer_op_swap(mixer, sample_count, mix); break;
|
||||
case MIX_ADD: mixer_op_add(mixer, sample_count, mix); break;
|
||||
case MIX_VOLUME: mixer_op_volume(mixer, sample_count, mix); break;
|
||||
case MIX_LIMIT: mixer_op_limit(mixer, sample_count, mix); break;
|
||||
case MIX_UPMIX: mixer_op_upmix(mixer, sample_count, mix); break;
|
||||
case MIX_DOWNMIX: mixer_op_downmix(mixer, sample_count, mix); break;
|
||||
case MIX_KILLMIX: mixer_op_killmix(mixer, sample_count, mix); break;
|
||||
case MIX_FADE: mixer_op_fade(mixer, sample_count, mix);
|
||||
case MIX_SWAP: mixer_op_swap(mixer, sbuf->filled, mix); break;
|
||||
case MIX_ADD: mixer_op_add(mixer, sbuf->filled, mix); break;
|
||||
case MIX_VOLUME: mixer_op_volume(mixer, sbuf->filled, mix); break;
|
||||
case MIX_LIMIT: mixer_op_limit(mixer, sbuf->filled, mix); break;
|
||||
case MIX_UPMIX: mixer_op_upmix(mixer, sbuf->filled, mix); break;
|
||||
case MIX_DOWNMIX: mixer_op_downmix(mixer, sbuf->filled, mix); break;
|
||||
case MIX_KILLMIX: mixer_op_killmix(mixer, sbuf->filled, mix); break;
|
||||
case MIX_FADE: mixer_op_fade(mixer, sbuf->filled, mix);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* downgrade mix to original output */
|
||||
sbuf_copy_f32_to_s16(outbuf, mixer->mixbuf, sample_count, mixer->output_channels);
|
||||
// setup + remix to output buf (buf is expected to be big enough to handle config)
|
||||
sbuf->channels = mixer->output_channels;
|
||||
if (mixer->force_type) {
|
||||
sbuf->fmt = mixer->force_type;
|
||||
}
|
||||
|
||||
sbuf_copy_from_f32(sbuf, mixer->mixbuf);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define _MIXER_H_
|
||||
|
||||
#include "../streamtypes.h"
|
||||
#include "sbuf.h"
|
||||
|
||||
typedef struct mixer_t mixer_t;
|
||||
|
||||
@ -10,7 +11,7 @@ typedef struct mixer_t mixer_t;
|
||||
mixer_t* mixer_init(int channels);
|
||||
void mixer_free(mixer_t* mixer);
|
||||
void mixer_update_channel(mixer_t* mixer);
|
||||
void mixer_process(mixer_t* mixer, sample_t *outbuf, int32_t sample_count, int32_t current_pos);
|
||||
void mixer_process(mixer_t* mixer, sbuf_t* sbuf, int32_t current_pos);
|
||||
bool mixer_is_active(mixer_t* mixer);
|
||||
|
||||
#endif
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define _MIXER_PRIV_H_
|
||||
#include "../streamtypes.h"
|
||||
#include "mixer.h"
|
||||
#include "sbuf.h"
|
||||
|
||||
#define VGMSTREAM_MAX_MIXING 512
|
||||
|
||||
@ -52,6 +53,7 @@ struct mixer_t {
|
||||
int current_channels; /* state: channels may increase/decrease during ops */
|
||||
int32_t current_subpos; /* state: current sample pos in the stream */
|
||||
|
||||
sfmt_t force_type;
|
||||
};
|
||||
|
||||
void mixer_op_swap(mixer_t* mixer, int32_t sample_count, mix_op_t* op);
|
||||
|
@ -1,12 +1,12 @@
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
#include "../vgmstream.h"
|
||||
#include "../util/channel_mappings.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "mixing.h"
|
||||
#include "mixer.h"
|
||||
#include "mixer_priv.h"
|
||||
#include "sbuf.h"
|
||||
#include "../layout/layout.h"
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
//TODO simplify
|
||||
/**
|
||||
@ -53,14 +53,14 @@ static int32_t get_current_pos(VGMSTREAM* vgmstream, int32_t sample_count) {
|
||||
return current_pos;
|
||||
}
|
||||
|
||||
void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
void mix_vgmstream(sbuf_t* sbuf, VGMSTREAM* vgmstream) {
|
||||
/* no support or not need to apply */
|
||||
if (!mixer_is_active(vgmstream->mixer))
|
||||
return;
|
||||
|
||||
int32_t current_pos = get_current_pos(vgmstream, sample_count);
|
||||
int32_t current_pos = get_current_pos(vgmstream, sbuf->filled);
|
||||
|
||||
mixer_process(vgmstream->mixer, outbuf, sample_count, current_pos);
|
||||
mixer_process(vgmstream->mixer, sbuf, current_pos);
|
||||
}
|
||||
|
||||
/* ******************************************************************* */
|
||||
@ -148,8 +148,11 @@ void mixing_info(VGMSTREAM* vgmstream, int* p_input_channels, int* p_output_chan
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
int input_channels, output_channels;
|
||||
|
||||
if (!mixer)
|
||||
goto fail;
|
||||
if (!mixer) {
|
||||
if (p_input_channels) *p_input_channels = vgmstream->channels;
|
||||
if (p_output_channels) *p_output_channels = vgmstream->channels;
|
||||
return;
|
||||
}
|
||||
|
||||
output_channels = mixer->output_channels;
|
||||
if (mixer->output_channels > vgmstream->channels)
|
||||
@ -159,11 +162,22 @@ void mixing_info(VGMSTREAM* vgmstream, int* p_input_channels, int* p_output_chan
|
||||
|
||||
if (p_input_channels) *p_input_channels = input_channels;
|
||||
if (p_output_channels) *p_output_channels = output_channels;
|
||||
|
||||
//;VGM_LOG("MIX: channels %i, in=%i, out=%i, mix=%i\n", vgmstream->channels, input_channels, output_channels, data->mixing_channels);
|
||||
return;
|
||||
fail:
|
||||
if (p_input_channels) *p_input_channels = vgmstream->channels;
|
||||
if (p_output_channels) *p_output_channels = vgmstream->channels;
|
||||
return;
|
||||
}
|
||||
|
||||
sfmt_t mixing_get_input_sample_type(VGMSTREAM* vgmstream) {
|
||||
// TODO: check vgmstream
|
||||
return SFMT_S16;
|
||||
}
|
||||
|
||||
sfmt_t mixing_get_output_sample_type(VGMSTREAM* vgmstream) {
|
||||
sfmt_t input_fmt = mixing_get_input_sample_type(vgmstream);
|
||||
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
if (!mixer)
|
||||
return input_fmt;
|
||||
|
||||
if (mixer->force_type)
|
||||
return mixer->force_type;
|
||||
|
||||
return input_fmt;
|
||||
}
|
||||
|
@ -2,11 +2,12 @@
|
||||
#define _MIXING_H_
|
||||
|
||||
#include "../vgmstream.h"
|
||||
#include "../util/log.h"
|
||||
#include "../util/log.h" //TODO remove
|
||||
#include "sbuf.h"
|
||||
|
||||
/* Applies mixing commands to the sample buffer. Mixing must be externally enabled and
|
||||
* outbuf must big enough to hold output_channels*samples_to_do */
|
||||
void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream);
|
||||
void mix_vgmstream(sbuf_t* sbuf, VGMSTREAM* vgmstream);
|
||||
|
||||
/* Call to let vgmstream apply mixing, which must handle input/output_channels.
|
||||
* Once mixing is active any new mixes are ignored (to avoid the possibility
|
||||
@ -14,7 +15,10 @@ void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream)
|
||||
void mixing_setup(VGMSTREAM* vgmstream, int32_t max_sample_count);
|
||||
|
||||
/* gets current mixing info */
|
||||
void mixing_info(VGMSTREAM* vgmstream, int *input_channels, int *output_channels);
|
||||
void mixing_info(VGMSTREAM* vgmstream, int* input_channels, int* output_channels);
|
||||
|
||||
sfmt_t mixing_get_input_sample_type(VGMSTREAM* vgmstream);
|
||||
sfmt_t mixing_get_output_sample_type(VGMSTREAM* vgmstream);
|
||||
|
||||
/* adds mixes filtering and optimizing if needed */
|
||||
void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src);
|
||||
@ -32,6 +36,7 @@ void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode)
|
||||
void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max);
|
||||
void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode);
|
||||
void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/);
|
||||
void mixing_macro_output_sample_format(VGMSTREAM* vgmstream, sfmt_t type);
|
||||
|
||||
|
||||
#endif
|
||||
|
@ -1,10 +1,10 @@
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
#include "../vgmstream.h"
|
||||
#include "../util/channel_mappings.h"
|
||||
#include "../layout/layout.h"
|
||||
#include "mixing.h"
|
||||
#include "mixer_priv.h"
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
#define MIX_MACRO_VOCALS 'v'
|
||||
@ -471,7 +471,7 @@ typedef enum {
|
||||
|
||||
void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_mapping*/) {
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
int ch, output_channels, mp_in, mp_out, ch_in, ch_out;
|
||||
int output_channels, mp_in, mp_out, ch_in, ch_out;
|
||||
channel_mapping_t input_mapping, output_mapping;
|
||||
const double vol_max = 1.0;
|
||||
const double vol_sqrt = 1 / sqrt(2);
|
||||
@ -534,7 +534,7 @@ void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_map
|
||||
|
||||
/* save and make N fake channels at the beginning for easier calcs */
|
||||
output_channels = mixer->output_channels;
|
||||
for (ch = 0; ch < max; ch++) {
|
||||
for (int ch = 0; ch < max; ch++) {
|
||||
mixing_push_upmix(vgmstream, 0);
|
||||
}
|
||||
|
||||
@ -542,13 +542,13 @@ void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_map
|
||||
ch_in = 0;
|
||||
for (mp_in = 0; mp_in < 16; mp_in++) {
|
||||
/* read input mapping (ex. 5.1) and find channel */
|
||||
if (!(input_mapping & (1<<mp_in)))
|
||||
if (!(input_mapping & (1 << mp_in)))
|
||||
continue;
|
||||
|
||||
ch_out = 0;
|
||||
for (mp_out = 0; mp_out < 16; mp_out++) {
|
||||
/* read output mapping (ex. 2.0) and find channel */
|
||||
if (!(output_mapping & (1<<mp_out)))
|
||||
if (!(output_mapping & (1 << mp_out)))
|
||||
continue;
|
||||
mixing_push_add(vgmstream, ch_out, max + ch_in, matrix[mp_in][mp_out]);
|
||||
|
||||
@ -565,3 +565,16 @@ void mixing_macro_downmix(VGMSTREAM* vgmstream, int max /*, mapping_t output_map
|
||||
/* remove unneeded channels */
|
||||
mixing_push_killmix(vgmstream, max);
|
||||
}
|
||||
|
||||
|
||||
void mixing_macro_output_sample_format(VGMSTREAM* vgmstream, sfmt_t type) {
|
||||
mixer_t* mixer = vgmstream->mixer;
|
||||
if (!mixer)
|
||||
return;
|
||||
|
||||
// optimization (may skip initializing mixer)
|
||||
sfmt_t input_fmt = mixing_get_input_sample_type(vgmstream);
|
||||
if (input_fmt == type)
|
||||
return;
|
||||
mixer->force_type = type;
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
if (vgmstream->current_sample > vgmstream->num_samples) {
|
||||
int channels = vgmstream->channels;
|
||||
|
||||
memset(buf, 0, sample_count * sizeof(sample_t) * channels);
|
||||
sbuf_silence_s16(buf, sample_count, channels, 0);
|
||||
return sample_count;
|
||||
}
|
||||
|
||||
@ -156,7 +156,8 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
excess = sample_count;
|
||||
decoded = sample_count - excess;
|
||||
|
||||
memset(buf + decoded * channels, 0, excess * sizeof(sample_t) * channels);
|
||||
sbuf_silence_s16(buf, sample_count, channels, decoded);
|
||||
|
||||
return sample_count;
|
||||
}
|
||||
|
||||
@ -165,79 +166,63 @@ int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
typedef struct {
|
||||
//sbuf_t sbuf;
|
||||
int16_t* tmpbuf;
|
||||
int samples_to_do;
|
||||
int samples_done;
|
||||
} render_helper_t;
|
||||
|
||||
|
||||
// consumes samples from decoder
|
||||
static void play_op_trim(VGMSTREAM* vgmstream, render_helper_t* renderer) {
|
||||
static void play_op_trim(VGMSTREAM* vgmstream, sbuf_t* sbuf) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
if (!ps->trim_begin_left)
|
||||
return;
|
||||
if (!renderer->samples_to_do)
|
||||
if (sbuf->samples <= 0)
|
||||
return;
|
||||
|
||||
// simpler using external buf?
|
||||
//sample_t* tmpbuf = vgmstream->tmpbuf;
|
||||
//size_t tmpbuf_size = vgmstream->tmpbuf_size;
|
||||
//int32_t buf_samples = tmpbuf_size / vgmstream->channels; /* base channels, no need to apply mixing */
|
||||
sample_t* tmpbuf = renderer->tmpbuf;
|
||||
int buf_samples = renderer->samples_to_do;
|
||||
|
||||
while (ps->trim_begin_left) {
|
||||
int to_do = ps->trim_begin_left;
|
||||
if (to_do > buf_samples)
|
||||
to_do = buf_samples;
|
||||
if (to_do > sbuf->samples)
|
||||
to_do = sbuf->samples;
|
||||
|
||||
render_layout(tmpbuf, to_do, vgmstream);
|
||||
render_layout(sbuf->buf, to_do, vgmstream);
|
||||
/* no mixing */
|
||||
ps->trim_begin_left -= to_do;
|
||||
}
|
||||
}
|
||||
|
||||
// adds empty samples to buf
|
||||
static void play_op_pad_begin(VGMSTREAM* vgmstream, render_helper_t* renderer) {
|
||||
static void play_op_pad_begin(VGMSTREAM* vgmstream, sbuf_t* sbuf) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
if (!ps->pad_begin_left)
|
||||
return;
|
||||
//if (ps->play_position > ps->play_begin_duration) //implicit
|
||||
// return;
|
||||
|
||||
int channels = ps->output_channels;
|
||||
int buf_samples = renderer->samples_to_do;
|
||||
|
||||
int to_do = ps->pad_begin_left;
|
||||
if (to_do > buf_samples)
|
||||
to_do = buf_samples;
|
||||
if (to_do > sbuf->samples)
|
||||
to_do = sbuf->samples;
|
||||
|
||||
sbuf_silence_part(sbuf, 0, to_do);
|
||||
|
||||
memset(renderer->tmpbuf, 0, to_do * sizeof(sample_t) * channels);
|
||||
ps->pad_begin_left -= to_do;
|
||||
|
||||
renderer->samples_done += to_do;
|
||||
renderer->samples_to_do -= to_do;
|
||||
renderer->tmpbuf += to_do * channels; /* as if mixed */
|
||||
sbuf->filled += to_do;
|
||||
}
|
||||
|
||||
// fades (modifies volumes) part of buf
|
||||
static void play_op_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
static void play_op_fade(VGMSTREAM* vgmstream, sbuf_t* sbuf) {
|
||||
play_config_t* pc = &vgmstream->config;
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
|
||||
if (pc->play_forever || !ps->fade_left)
|
||||
return;
|
||||
if (ps->play_position + samples_done < ps->fade_start)
|
||||
if (ps->play_position + sbuf->filled < ps->fade_start)
|
||||
return;
|
||||
|
||||
int start, fade_pos;
|
||||
int channels = ps->output_channels;
|
||||
int32_t to_do = ps->fade_left;
|
||||
|
||||
if (ps->play_position < ps->fade_start) {
|
||||
start = samples_done - (ps->play_position + samples_done - ps->fade_start);
|
||||
start = sbuf->filled - (ps->play_position + sbuf->filled - ps->fade_start);
|
||||
fade_pos = 0;
|
||||
}
|
||||
else {
|
||||
@ -245,62 +230,51 @@ static void play_op_fade(VGMSTREAM* vgmstream, sample_t* buf, int samples_done)
|
||||
fade_pos = ps->play_position - ps->fade_start;
|
||||
}
|
||||
|
||||
if (to_do > samples_done - start)
|
||||
to_do = samples_done - start;
|
||||
if (to_do > sbuf->filled - start)
|
||||
to_do = sbuf->filled - start;
|
||||
|
||||
//TODO: use delta fadedness to improve performance?
|
||||
for (int s = start; s < start + to_do; s++, fade_pos++) {
|
||||
double fadedness = (double)(ps->fade_duration - fade_pos) / ps->fade_duration;
|
||||
for (int ch = 0; ch < channels; ch++) {
|
||||
buf[s * channels + ch] = (sample_t)(buf[s*channels + ch] * fadedness);
|
||||
}
|
||||
}
|
||||
sbuf_fadeout(sbuf, start, to_do, fade_pos, ps->fade_duration);
|
||||
|
||||
ps->fade_left -= to_do;
|
||||
|
||||
/* next samples after fade end would be pad end/silence, so we can just memset */
|
||||
memset(buf + (start + to_do) * channels, 0, (samples_done - to_do - start) * sizeof(sample_t) * channels);
|
||||
}
|
||||
|
||||
// adds null samples after decode
|
||||
// pad-end works like fades, where part of buf is samples and part is padding (blank)
|
||||
// (beyond pad end normally is silence, except with segmented layout)
|
||||
static int play_op_pad_end(VGMSTREAM* vgmstream, sample_t* buf, int samples_done) {
|
||||
static void play_op_pad_end(VGMSTREAM* vgmstream, sbuf_t* sbuf) {
|
||||
play_config_t* pc = &vgmstream->config;
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
|
||||
if (pc->play_forever)
|
||||
return 0;
|
||||
if (samples_done == 0)
|
||||
return 0;
|
||||
if (ps->play_position + samples_done < ps->pad_end_start)
|
||||
return 0;
|
||||
|
||||
int channels = vgmstream->pstate.output_channels;
|
||||
int skip = 0;
|
||||
int32_t to_do;
|
||||
return;
|
||||
if (ps->play_position + sbuf->filled < ps->pad_end_start)
|
||||
return;
|
||||
|
||||
int start;
|
||||
int to_do;
|
||||
if (ps->play_position < ps->pad_end_start) {
|
||||
skip = ps->pad_end_start - ps->play_position;
|
||||
start = ps->pad_end_start - ps->play_position;
|
||||
to_do = ps->pad_end_duration;
|
||||
}
|
||||
else {
|
||||
skip = 0;
|
||||
start = 0;
|
||||
to_do = (ps->pad_end_start + ps->pad_end_duration) - ps->play_position;
|
||||
}
|
||||
|
||||
if (to_do > samples_done - skip)
|
||||
to_do = samples_done - skip;
|
||||
if (to_do > sbuf->filled - start)
|
||||
to_do = sbuf->filled - start;
|
||||
|
||||
memset(buf + (skip * channels), 0, to_do * sizeof(sample_t) * channels);
|
||||
return skip + to_do;
|
||||
sbuf_silence_part(sbuf, start, to_do);
|
||||
|
||||
//TO-DO: this never adds samples and just silences them, since decoder always returns something
|
||||
//sbuf->filled += ?
|
||||
}
|
||||
|
||||
// clamp final play_position + done samples. Probably doesn't matter, but just in case.
|
||||
static void play_adjust_totals(VGMSTREAM* vgmstream, render_helper_t* renderer, int sample_count) {
|
||||
static void play_adjust_totals(VGMSTREAM* vgmstream, sbuf_t* sbuf, int sample_count) {
|
||||
play_state_t* ps = &vgmstream->pstate;
|
||||
|
||||
ps->play_position += renderer->samples_done;
|
||||
ps->play_position += sbuf->filled;
|
||||
|
||||
/* usually only happens when mixing layers of different lengths (where decoder keeps calling render) */
|
||||
if (!vgmstream->config.play_forever && ps->play_position > ps->play_duration) {
|
||||
@ -308,7 +282,7 @@ static void play_adjust_totals(VGMSTREAM* vgmstream, render_helper_t* renderer,
|
||||
if (excess > sample_count)
|
||||
excess = sample_count;
|
||||
|
||||
renderer->samples_done = (sample_count - excess);
|
||||
sbuf->filled = (sample_count - excess);
|
||||
|
||||
ps->play_position = ps->play_duration;
|
||||
}
|
||||
@ -324,51 +298,51 @@ static void play_adjust_totals(VGMSTREAM* vgmstream, render_helper_t* renderer,
|
||||
* \ end-fade |
|
||||
*
|
||||
* Which part we are in depends on play_position. Because vgmstream render's
|
||||
* buf may fall anywhere in the middle of all that. Since some ops add "fake" (non-decoded)
|
||||
* samples to buf, we need to
|
||||
* buf may fall anywhere in the middle of all that. Some ops add "fake" (non-decoded)
|
||||
* samples to buf.
|
||||
*/
|
||||
|
||||
int render_vgmstream(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
render_helper_t renderer = {0};
|
||||
renderer.tmpbuf = buf;
|
||||
renderer.samples_done = 0;
|
||||
renderer.samples_to_do = sample_count;
|
||||
sbuf_t sbuf = {0};
|
||||
sbuf_init_s16(&sbuf, buf, sample_count, vgmstream->channels);
|
||||
|
||||
//sbuf_init16(&renderer.sbuf, buf, sample_count, vgmstream->channels);
|
||||
|
||||
|
||||
/* simple mode with no settings (just skip everything below) */
|
||||
/* simple mode with no play settings (just skip everything below) */
|
||||
if (!vgmstream->config_enabled) {
|
||||
render_layout(buf, renderer.samples_to_do, vgmstream);
|
||||
mix_vgmstream(buf, renderer.samples_to_do, vgmstream);
|
||||
return renderer.samples_to_do;
|
||||
render_layout(buf, sample_count, vgmstream);
|
||||
sbuf.filled = sample_count;
|
||||
|
||||
mix_vgmstream(&sbuf, vgmstream);
|
||||
|
||||
return sample_count;
|
||||
}
|
||||
|
||||
|
||||
/* adds empty samples to buf and moves it */
|
||||
play_op_pad_begin(vgmstream, &renderer);
|
||||
/* trim decoder output (may go anywhere before main render since it doesn't use render output, but easier first) */
|
||||
play_op_trim(vgmstream, &sbuf);
|
||||
|
||||
/* trim decoder output (may go anywhere before main render since it doesn't use render output) */
|
||||
play_op_trim(vgmstream, &renderer);
|
||||
/* adds empty samples to buf and moves it */
|
||||
play_op_pad_begin(vgmstream, &sbuf);
|
||||
|
||||
|
||||
/* main decode */
|
||||
int done = render_layout(renderer.tmpbuf, renderer.samples_to_do, vgmstream);
|
||||
mix_vgmstream(renderer.tmpbuf, done, vgmstream);
|
||||
sbuf_t sbuf_tmp = sbuf;
|
||||
sbuf_consume(&sbuf_tmp, sbuf_tmp.filled);
|
||||
int done = render_layout(sbuf_tmp.buf, sbuf_tmp.samples, vgmstream);
|
||||
sbuf.filled += done;
|
||||
|
||||
mix_vgmstream(&sbuf, vgmstream);
|
||||
sbuf.channels = vgmstream->pstate.output_channels;
|
||||
|
||||
|
||||
/* simple fadeout over decoded data (after mixing since usually results in less samples) */
|
||||
play_op_fade(vgmstream, renderer.tmpbuf, done);
|
||||
play_op_fade(vgmstream, &sbuf);
|
||||
|
||||
|
||||
/* silence leftover buf samples (after fade as rarely may mix decoded buf + trim samples when no fade is set)
|
||||
* (could be done before render to "consume" buf but doesn't matter much) */
|
||||
play_op_pad_end(vgmstream, renderer.tmpbuf, done);
|
||||
|
||||
renderer.samples_done += done;
|
||||
//renderer.samples_to_do -= done; //not useful at this point
|
||||
//renderer.tmpbuf += done * vgmstream->pstate.output_channels;
|
||||
play_op_pad_end(vgmstream, &sbuf);
|
||||
|
||||
|
||||
play_adjust_totals(vgmstream, &renderer, sample_count);
|
||||
return renderer.samples_done;
|
||||
play_adjust_totals(vgmstream, &sbuf, sample_count);
|
||||
return sbuf.filled;
|
||||
}
|
||||
|
220
src/base/sbuf.c
220
src/base/sbuf.c
@ -1,43 +1,157 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
//#include <math.h>
|
||||
#include "../util.h"
|
||||
#include "sbuf.h"
|
||||
|
||||
#if 0
|
||||
/* skips N samples from current sbuf */
|
||||
void sbuf_init16(sbuf_t* sbuf, int16_t* buf, int samples, int channels) {
|
||||
|
||||
void sbuf_init_s16(sbuf_t* sbuf, int16_t* buf, int samples, int channels) {
|
||||
memset(sbuf, 0, sizeof(sbuf_t));
|
||||
sbuf->buf = buf;
|
||||
sbuf->samples = samples;
|
||||
sbuf->channels = channels;
|
||||
sbuf->fmt = SFMT_S16;
|
||||
}
|
||||
|
||||
void sbuf_init_f32(sbuf_t* sbuf, float* buf, int samples, int channels) {
|
||||
memset(sbuf, 0, sizeof(sbuf_t));
|
||||
sbuf->buf = buf;
|
||||
sbuf->samples = samples;
|
||||
sbuf->channels = channels;
|
||||
sbuf->fmt = SFMT_F32;
|
||||
}
|
||||
|
||||
|
||||
int sfmt_get_sample_size(sfmt_t fmt) {
|
||||
switch(fmt) {
|
||||
case SFMT_F32:
|
||||
case SFMT_FLT:
|
||||
return 0x04;
|
||||
case SFMT_S16:
|
||||
return 0x02;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
void* sbuf_get_filled_buf(sbuf_t* sbuf) {
|
||||
int sample_size = sfmt_get_sample_size(sbuf->fmt);
|
||||
|
||||
uint8_t* buf = sbuf->buf;
|
||||
buf += sbuf->filled * sbuf->channels * sample_size;
|
||||
return buf;
|
||||
}
|
||||
#endif
|
||||
|
||||
void sbuf_consume(sbuf_t* sbuf, int count) {
|
||||
int sample_size = sfmt_get_sample_size(sbuf->fmt);
|
||||
if (sample_size <= 0)
|
||||
return;
|
||||
if (count > sbuf->samples || count > sbuf->filled) //TODO?
|
||||
return;
|
||||
|
||||
// TODO decide if using float 1.0 style or 32767 style (fuzzy PCM changes when doing that)
|
||||
void sbuf_copy_s16_to_f32(float* buf_f32, int16_t* buf_s16, int samples, int channels) {
|
||||
for (int s = 0; s < samples * channels; s++) {
|
||||
buf_f32[s] = (float)buf_s16[s]; // / 32767.0f
|
||||
uint8_t* buf = sbuf->buf;
|
||||
buf += count * sbuf->channels * sample_size;
|
||||
|
||||
sbuf->buf = buf;
|
||||
sbuf->filled -= count;
|
||||
sbuf->samples -= count;
|
||||
}
|
||||
|
||||
/* when casting float to int, value is simply truncated:
|
||||
* - (int)1.7 = 1, (int)-1.7 = -1
|
||||
* alts for more accurate rounding could be:
|
||||
* - (int)floor(f)
|
||||
* - (int)(f < 0 ? f - 0.5f : f + 0.5f)
|
||||
* - (((int) (f1 + 32768.5)) - 32768)
|
||||
* - etc
|
||||
* but since +-1 isn't really audible we'll just cast, as it's the fastest
|
||||
*
|
||||
* Regular C float-to-int casting ("int i = (int)f") is somewhat slow due to IEEE
|
||||
* float requirements, but C99 adds some faster-but-less-precise casting functions.
|
||||
* MSVC added this in VS2015 (_MSC_VER 1900) but doesn't seem inlined and is very slow.
|
||||
* It's slightly faster (~5%) but causes fuzzy PCM<>float<>PCM conversions.
|
||||
*/
|
||||
static inline int float_to_int(float val) {
|
||||
#if 1
|
||||
return (int)val;
|
||||
#elif defined(_MSC_VER)
|
||||
return (int)val;
|
||||
#else
|
||||
return lrintf(val);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int double_to_int(double val) {
|
||||
#if 1
|
||||
return (int)val;
|
||||
#elif defined(_MSC_VER)
|
||||
return (int)val;
|
||||
#else
|
||||
return lrint(val);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline float double_to_float(double val) {
|
||||
return (float)val;
|
||||
}
|
||||
|
||||
//TODO decide if using float 1.0 style or 32767 style (fuzzy PCM when doing that)
|
||||
//TODO: maybe use macro-style templating (but kinda ugly)
|
||||
void sbuf_copy_to_f32(float* dst, sbuf_t* sbuf) {
|
||||
|
||||
switch(sbuf->fmt) {
|
||||
case SFMT_S16: {
|
||||
int16_t* src = sbuf->buf;
|
||||
for (int s = 0; s < sbuf->filled * sbuf->channels; s++) {
|
||||
dst[s] = (float)src[s]; // / 32767.0f
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SFMT_FLT:
|
||||
case SFMT_F32: {
|
||||
float* src = sbuf->buf;
|
||||
for (int s = 0; s < sbuf->filled * sbuf->channels; s++) {
|
||||
dst[s] = src[s];
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels) {
|
||||
/* when casting float to int, value is simply truncated:
|
||||
* - (int)1.7 = 1, (int)-1.7 = -1
|
||||
* alts for more accurate rounding could be:
|
||||
* - (int)floor(f)
|
||||
* - (int)(f < 0 ? f - 0.5f : f + 0.5f)
|
||||
* - (((int) (f1 + 32768.5)) - 32768)
|
||||
* - etc
|
||||
* but since +-1 isn't really audible we'll just cast, as it's the fastest
|
||||
*/
|
||||
for (int s = 0; s < samples * channels; s++) {
|
||||
buf_s16[s] = clamp16( buf_f32[s]); // * 32767.0f
|
||||
void sbuf_copy_from_f32(sbuf_t* sbuf, float* src) {
|
||||
switch(sbuf->fmt) {
|
||||
case SFMT_S16: {
|
||||
int16_t* dst = sbuf->buf;
|
||||
for (int s = 0; s < sbuf->filled * sbuf->channels; s++) {
|
||||
dst[s] = clamp16(float_to_int(src[s]));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SFMT_F32: {
|
||||
float* dst = sbuf->buf;
|
||||
for (int s = 0; s < sbuf->filled * sbuf->channels; s++) {
|
||||
dst[s] = src[s];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SFMT_FLT: {
|
||||
float* dst = sbuf->buf;
|
||||
for (int s = 0; s < sbuf->filled * sbuf->channels; s++) {
|
||||
dst[s] = src[s] / 32768.0f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void sbuf_copy_samples(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled) {
|
||||
void sbuf_copy_segments(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled) {
|
||||
int pos = samples_filled * dst_channels;
|
||||
|
||||
if (src_channels == dst_channels) { /* most common and probably faster */
|
||||
@ -72,10 +186,6 @@ void sbuf_copy_layers(sample_t* dst, int dst_channels, sample_t* src, int src_ch
|
||||
}
|
||||
}
|
||||
|
||||
void sbuf_silence(sample_t* dst, int samples, int channels, int filled) {
|
||||
memset(dst + filled * channels, 0, (samples - filled) * channels * sizeof(sample_t));
|
||||
}
|
||||
|
||||
bool sbuf_realloc(sample_t** dst, int samples, int channels) {
|
||||
sample_t* outbuf_re = realloc(*dst, samples * channels * sizeof(sample_t));
|
||||
if (!outbuf_re) return false;
|
||||
@ -83,3 +193,65 @@ bool sbuf_realloc(sample_t** dst, int samples, int channels) {
|
||||
*dst = outbuf_re;
|
||||
return true;
|
||||
}
|
||||
|
||||
void sbuf_silence_s16(sample_t* dst, int samples, int channels, int filled) {
|
||||
memset(dst + filled * channels, 0, (samples - filled) * channels * sizeof(sample_t));
|
||||
}
|
||||
|
||||
void sbuf_silence_part(sbuf_t* sbuf, int from, int count) {
|
||||
int sample_size = sfmt_get_sample_size(sbuf->fmt);
|
||||
|
||||
uint8_t* buf = sbuf->buf;
|
||||
buf += from * sbuf->channels * sample_size;
|
||||
memset(buf, 0, count * sbuf->channels * sample_size);
|
||||
}
|
||||
|
||||
void sbuf_silence_rest(sbuf_t* sbuf) {
|
||||
sbuf_silence_part(sbuf, sbuf->filled, sbuf->samples - sbuf->filled);
|
||||
}
|
||||
|
||||
void sbuf_fadeout(sbuf_t* sbuf, int start, int to_do, int fade_pos, int fade_duration) {
|
||||
//TODO: use interpolated fadedness to improve performance?
|
||||
//TODO: use float fadedness?
|
||||
|
||||
int s = start * sbuf->channels;
|
||||
int s_end = (start + to_do) * sbuf->channels;
|
||||
|
||||
|
||||
switch(sbuf->fmt) {
|
||||
case SFMT_S16: {
|
||||
int16_t* buf = sbuf->buf;
|
||||
while (s < s_end) {
|
||||
double fadedness = (double)(fade_duration - fade_pos) / fade_duration;
|
||||
fade_pos++;
|
||||
|
||||
for (int ch = 0; ch < sbuf->channels; ch++) {
|
||||
buf[s] = double_to_int(buf[s] * fadedness);
|
||||
s++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SFMT_FLT:
|
||||
case SFMT_F32: {
|
||||
float* buf = sbuf->buf;
|
||||
while (s < s_end) {
|
||||
double fadedness = (double)(fade_duration - fade_pos) / fade_duration;
|
||||
fade_pos++;
|
||||
|
||||
for (int ch = 0; ch < sbuf->channels; ch++) {
|
||||
buf[s] = double_to_float(buf[s] * fadedness);
|
||||
s++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* next samples after fade end would be pad end/silence */
|
||||
int count = sbuf->filled - (start + to_do);
|
||||
sbuf_silence_part(sbuf, start + to_do, count);
|
||||
}
|
||||
|
@ -3,50 +3,53 @@
|
||||
|
||||
#include "../streamtypes.h"
|
||||
|
||||
#if 0
|
||||
/* interleaved: buffer for all channels = [ch*s] = (ch1 ch2 ch1 ch2 ch1 ch2 ch1 ch2 ...) */
|
||||
/* planar: buffer per channel = [ch][s] = (c1 c1 c1 c1 ...) (c2 c2 c2 c2 ...) */
|
||||
/* All types are interleaved (buffer for all channels = [ch*s] = ch1 ch2 ch1 ch2 ch1 ch2 ...)
|
||||
* rather than planar (buffer per channel = [ch][s] = c1 c1 c1 c1 ... c2 c2 c2 c2 ...) */
|
||||
typedef enum {
|
||||
SFMT_NONE,
|
||||
SFMT_S16,
|
||||
SFMT_F32,
|
||||
SFMT_S16, /* standard PCM16 */
|
||||
//SFMT_S24,
|
||||
//SFMT_S32,
|
||||
//SFMT_S16P,
|
||||
//SFMT_F32P,
|
||||
SFMT_F32, /* pcm-like float (+-32768), for internal use (simpler pcm > f32 plus some decoders use this) */
|
||||
SFMT_FLT, /* standard float (+-1.0), for external players */
|
||||
} sfmt_t;
|
||||
|
||||
|
||||
/* simple buffer info to pass around, for internal mixing
|
||||
* meant to held existing sound buffer pointers rather than alloc'ing directly (some ops will swap/move its internals) */
|
||||
typedef struct {
|
||||
void* buf; /* current sample buffer */
|
||||
int samples; /* max samples */
|
||||
int channels; /* interleaved step or planar buffers */
|
||||
sfmt_t fmt; /* buffer type */
|
||||
//int filled; /* samples in buffer */
|
||||
//int planar;
|
||||
int channels; /* interleaved step or planar buffers */
|
||||
int samples; /* max samples */
|
||||
int filled; /* samples in buffer */
|
||||
} sbuf_t;
|
||||
|
||||
void sbuf_init16(sbuf_t* sbuf, int16_t* buf, int samples, int channels);
|
||||
/* it's probably slightly faster to make some function inline'd, but aren't called that often to matter (given big enough total samples) */
|
||||
|
||||
void sbuf_clamp(sbuf_t* sbuf, int samples);
|
||||
void sbuf_init_s16(sbuf_t* sbuf, int16_t* buf, int samples, int channels);
|
||||
void sbuf_init_f32(sbuf_t* sbuf, float* buf, int samples, int channels);
|
||||
|
||||
/* skips N samples from current sbuf */
|
||||
void sbuf_consume(sbuf_t* sbuf, int samples);
|
||||
#endif
|
||||
int sfmt_get_sample_size(sfmt_t fmt);
|
||||
|
||||
/* it's probably slightly faster to make those inline'd, but aren't called that often to matter (given big enough total samples) */
|
||||
//void* sbuf_get_filled_buf(sbuf_t* sbuf);
|
||||
//void sbuf_clamp(sbuf_t* sbuf, int samples);
|
||||
|
||||
/* move buf by samples amount to simplify some code (will lose base buf pointer) */
|
||||
void sbuf_consume(sbuf_t* sbuf, int count);
|
||||
|
||||
// TODO decide if using float 1.0 style or 32767 style (fuzzy PCM changes when doing that)
|
||||
void sbuf_copy_s16_to_f32(float* buf_f32, int16_t* buf_s16, int samples, int channels);
|
||||
|
||||
void sbuf_copy_f32_to_s16(int16_t* buf_s16, float* buf_f32, int samples, int channels);
|
||||
|
||||
void sbuf_copy_samples(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled);
|
||||
void sbuf_copy_to_f32(float* dst, sbuf_t* sbuf);
|
||||
void sbuf_copy_from_f32(sbuf_t* sbuf, float* src);
|
||||
|
||||
void sbuf_copy_segments(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled);
|
||||
void sbuf_copy_layers(sample_t* dst, int dst_channels, sample_t* src, int src_channels, int samples_to_do, int samples_filled, int dst_ch_start);
|
||||
|
||||
void sbuf_silence(sample_t* dst, int samples, int channels, int filled);
|
||||
|
||||
bool sbuf_realloc(sample_t** dst, int samples, int channels);
|
||||
|
||||
void sbuf_silence_s16(sample_t* dst, int samples, int channels, int filled);
|
||||
void sbuf_silence_rest(sbuf_t* sbuf);
|
||||
void sbuf_silence_part(sbuf_t* sbuf, int from, int count);
|
||||
|
||||
void sbuf_fadeout(sbuf_t* sbuf, int start, int to_do, int fade_pos, int fade_duration);
|
||||
|
||||
#endif
|
||||
|
@ -1,22 +1,22 @@
|
||||
#include "api_internal.h"
|
||||
#if LIBVGMSTREAM_ENABLE
|
||||
/* STREAMFILE for internal use, that bridges calls to external libvgmstream_streamfile_t */
|
||||
/* STREAMFILE for internal use, that bridges calls to external libstreamfile_t */
|
||||
|
||||
|
||||
static STREAMFILE* open_api_streamfile_internal(libvgmstream_streamfile_t* libsf, bool external_libsf);
|
||||
static STREAMFILE* open_api_streamfile_internal(libstreamfile_t* libsf, bool external_libsf);
|
||||
|
||||
|
||||
typedef struct {
|
||||
STREAMFILE vt;
|
||||
|
||||
libvgmstream_streamfile_t* libsf;
|
||||
libstreamfile_t* libsf;
|
||||
bool external_libsf; //TODO: improve
|
||||
} API_STREAMFILE;
|
||||
|
||||
static size_t api_read(API_STREAMFILE* sf, uint8_t* dst, offv_t offset, size_t length) {
|
||||
void* user_data = sf->libsf->user_data;
|
||||
|
||||
sf->libsf->seek(sf->libsf->user_data, offset, LIBVGMSTREAM_STREAMFILE_SEEK_SET);
|
||||
sf->libsf->seek(sf->libsf->user_data, offset, LIBSTREAMFILE_SEEK_SET);
|
||||
return sf->libsf->read(user_data, dst, length);
|
||||
}
|
||||
|
||||
@ -46,11 +46,11 @@ static void api_get_name(API_STREAMFILE* sf, char* name, size_t name_size) {
|
||||
}
|
||||
|
||||
static STREAMFILE* api_open(API_STREAMFILE* sf, const char* filename, size_t buf_size) {
|
||||
libvgmstream_streamfile_t* libsf = sf->libsf->open(sf->libsf->user_data, filename);
|
||||
libstreamfile_t* libsf = sf->libsf->open(sf->libsf->user_data, filename);
|
||||
STREAMFILE* new_sf = open_api_streamfile_internal(libsf, false);
|
||||
|
||||
if (!new_sf) {
|
||||
libvgmstream_streamfile_close(libsf);
|
||||
libstreamfile_close(libsf);
|
||||
}
|
||||
|
||||
return new_sf;
|
||||
@ -63,7 +63,7 @@ static void api_close(API_STREAMFILE* sf) {
|
||||
free(sf);
|
||||
}
|
||||
|
||||
static STREAMFILE* open_api_streamfile_internal(libvgmstream_streamfile_t* libsf, bool external_libsf) {
|
||||
static STREAMFILE* open_api_streamfile_internal(libstreamfile_t* libsf, bool external_libsf) {
|
||||
API_STREAMFILE* this_sf = NULL;
|
||||
|
||||
if (!libsf)
|
||||
@ -87,7 +87,7 @@ static STREAMFILE* open_api_streamfile_internal(libvgmstream_streamfile_t* libsf
|
||||
return &this_sf->vt;
|
||||
}
|
||||
|
||||
STREAMFILE* open_api_streamfile(libvgmstream_streamfile_t* libsf) {
|
||||
STREAMFILE* open_api_streamfile(libstreamfile_t* libsf) {
|
||||
return open_api_streamfile_internal(libsf, true);
|
||||
}
|
||||
|
||||
|
@ -698,7 +698,7 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL* stream, mpeg_codec_data* d
|
||||
|
||||
|
||||
bytes_filled = sizeof(sample_t) * ms->samples_filled * channels_per_frame;
|
||||
if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) {
|
||||
if (bytes_filled + eaf->pcm_size > ms->sbuf_size) {
|
||||
VGM_LOG("EAL3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size);
|
||||
goto fail;
|
||||
}
|
||||
@ -709,10 +709,12 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL* stream, mpeg_codec_data* d
|
||||
}
|
||||
|
||||
if (eaf->v1_pcm_samples || eaf->v1_offset_samples) {
|
||||
uint8_t* outbuf = ms->output_buffer + bytes_filled;
|
||||
uint8_t* outbuf = ms->sbuf;
|
||||
off_t pcm_offset = stream->offset + eaf->pre_size + eaf->common_size;
|
||||
size_t decode_to_discard;
|
||||
|
||||
outbuf += bytes_filled;
|
||||
|
||||
VGM_ASSERT(eaf->v1_offset_samples > 576, "EAL3: big discard %i at 0x%x\n", eaf->v1_offset_samples, (uint32_t)stream->offset);
|
||||
VGM_ASSERT(eaf->v1_pcm_samples > 0x100, "EAL3: big samples %i at 0x%x\n", eaf->v1_pcm_samples, (uint32_t)stream->offset);
|
||||
VGM_ASSERT(eaf->v1_offset_samples > 0 && eaf->v1_pcm_samples == 0, "EAL3: offset_samples without pcm_samples\n"); /* not seen but could work */
|
||||
@ -742,10 +744,12 @@ static int ealayer3_write_pcm_block(VGMSTREAMCHANNEL* stream, mpeg_codec_data* d
|
||||
}
|
||||
|
||||
if (eaf->v2_extended_flag) {
|
||||
uint8_t* outbuf = ms->output_buffer + bytes_filled;
|
||||
uint8_t* outbuf = ms->sbuf;
|
||||
off_t pcm_offset = stream->offset + eaf->pre_size + eaf->common_size;
|
||||
size_t usable_samples, decode_to_discard;
|
||||
|
||||
outbuf += bytes_filled;
|
||||
|
||||
/* V2P usually only copies big PCM, while V2S discards then copies few samples (similar to V1b).
|
||||
* Unlike V1b, both modes seem to use 'packed' PCM block */
|
||||
ealayer3_copy_pcm_block(outbuf, pcm_offset, eaf->v2_pcm_samples, channels_per_frame, 1, stream->streamfile);
|
||||
|
@ -131,7 +131,7 @@ static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data
|
||||
|
||||
|
||||
bytes_filled = sizeof(sample_t) * ms->samples_filled * data->channels_per_frame;
|
||||
if (bytes_filled + eaf->pcm_size > ms->output_buffer_size) {
|
||||
if (bytes_filled + eaf->pcm_size > ms->sbuf_size) {
|
||||
VGM_LOG("EAMP3: can't fill the sample buffer with 0x%x\n", eaf->pcm_size);
|
||||
goto fail;
|
||||
}
|
||||
@ -143,7 +143,8 @@ static int eamp3_write_pcm_block(VGMSTREAMCHANNEL *stream, mpeg_codec_data *data
|
||||
for (i = 0; i < eaf->pcm_number * data->channels_per_frame; i++) {
|
||||
off_t pcm_offset = stream->offset + eaf->pre_size + eaf->mpeg_size + sizeof(sample_t)*i;
|
||||
int16_t pcm_sample = read_s16le(pcm_offset,stream->streamfile);
|
||||
put_s16le(ms->output_buffer + bytes_filled + sizeof(sample_t) * i, pcm_sample);
|
||||
uint8_t* sbuf = ms->sbuf;
|
||||
put_s16le(sbuf + bytes_filled + sizeof(sample_t) * i, pcm_sample);
|
||||
}
|
||||
ms->samples_filled += eaf->pcm_number;
|
||||
|
||||
|
@ -170,9 +170,9 @@ mpeg_codec_data* init_mpeg_custom(STREAMFILE* sf, off_t start_offset, coding_t*
|
||||
if (!data->streams[i].handle) goto fail;
|
||||
|
||||
/* size could be any value */
|
||||
data->streams[i].output_buffer_size = sizeof(sample_t) * data->channels_per_frame * data->samples_per_frame;
|
||||
data->streams[i].output_buffer = calloc(data->streams[i].output_buffer_size, sizeof(uint8_t));
|
||||
if (!data->streams[i].output_buffer) goto fail;
|
||||
data->streams[i].sbuf_size = sizeof(sample_t) * data->channels_per_frame * data->samples_per_frame;
|
||||
data->streams[i].sbuf = calloc(data->streams[i].sbuf_size, sizeof(uint8_t));
|
||||
if (!data->streams[i].sbuf) goto fail;
|
||||
|
||||
/* one per stream as sometimes mpg123 can't read the whole buffer in one pass */
|
||||
data->streams[i].buffer_size = data->default_buffer_size;
|
||||
@ -246,16 +246,15 @@ void decode_mpeg(VGMSTREAM* vgmstream, sample_t* outbuf, int32_t samples_to_do,
|
||||
* Feeds raw data and extracts decoded samples as needed.
|
||||
*/
|
||||
static void decode_mpeg_standard(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data, sample_t* outbuf, int32_t samples_to_do, int channels) {
|
||||
int samples_done = 0;
|
||||
unsigned char *outbytes = (unsigned char *)outbuf;
|
||||
|
||||
int samples_done = 0;
|
||||
while (samples_done < samples_to_do) {
|
||||
size_t bytes_done;
|
||||
int rc, bytes_to_do;
|
||||
|
||||
/* read more raw data */
|
||||
if (!data->buffer_full) {
|
||||
data->bytes_in_buffer = read_streamfile(data->buffer,stream->offset,data->buffer_size,stream->streamfile);
|
||||
data->bytes_in_buffer = read_streamfile(data->buffer, stream->offset, data->buffer_size, stream->streamfile);
|
||||
|
||||
/* end of stream, fill rest with 0s */
|
||||
if (data->bytes_in_buffer <= 0) {
|
||||
@ -264,32 +263,32 @@ static void decode_mpeg_standard(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data
|
||||
break;
|
||||
}
|
||||
|
||||
data->buffer_full = 1;
|
||||
data->buffer_used = 0;
|
||||
data->buffer_full = true;
|
||||
data->buffer_used = false;
|
||||
|
||||
stream->offset += data->bytes_in_buffer;
|
||||
}
|
||||
|
||||
bytes_to_do = (samples_to_do-samples_done)*sizeof(sample_t)*channels;
|
||||
bytes_to_do = (samples_to_do-samples_done) * channels * sizeof(sample_t);
|
||||
|
||||
/* feed new raw data to the decoder if needed, copy decoded results to output */
|
||||
if (!data->buffer_used) {
|
||||
rc = mpg123_decode(data->m, data->buffer,data->bytes_in_buffer, outbytes, bytes_to_do, &bytes_done);
|
||||
data->buffer_used = 1;
|
||||
rc = mpg123_decode(data->m, data->buffer, data->bytes_in_buffer, outbuf, bytes_to_do, &bytes_done);
|
||||
data->buffer_used = true;
|
||||
}
|
||||
else {
|
||||
rc = mpg123_decode(data->m, NULL,0, outbytes, bytes_to_do, &bytes_done);
|
||||
rc = mpg123_decode(data->m, NULL,0, outbuf, bytes_to_do, &bytes_done);
|
||||
}
|
||||
|
||||
/* not enough raw data, request more */
|
||||
if (rc == MPG123_NEED_MORE) {
|
||||
data->buffer_full = 0;
|
||||
data->buffer_full = false;
|
||||
}
|
||||
VGM_ASSERT(rc != MPG123_NEED_MORE && rc != MPG123_OK, "MPEG: error %i\n", rc);
|
||||
|
||||
/* update copied samples */
|
||||
samples_done += bytes_done / sizeof(sample_t) / channels;
|
||||
outbytes += bytes_done;
|
||||
outbuf += bytes_done / sizeof(sample_t);
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,14 +300,14 @@ static void decode_mpeg_standard(VGMSTREAMCHANNEL* stream, mpeg_codec_data* data
|
||||
. Depletes the stream's sample buffers before decoding more, so it doesn't run out of buffer space.
|
||||
*/
|
||||
static void decode_mpeg_custom(VGMSTREAM* vgmstream, mpeg_codec_data* data, sample_t* outbuf, int32_t samples_to_do, int channels) {
|
||||
int i, samples_done = 0;
|
||||
int samples_done = 0;
|
||||
|
||||
while (samples_done < samples_to_do) {
|
||||
int samples_to_copy = -1;
|
||||
|
||||
/* find max to copy from all streams (equal for all channels) */
|
||||
for (i = 0; i < data->streams_size; i++) {
|
||||
size_t samples_in_stream = data->streams[i].samples_filled - data->streams[i].samples_used;
|
||||
for (int i = 0; i < data->streams_size; i++) {
|
||||
size_t samples_in_stream = data->streams[i].samples_filled - data->streams[i].samples_used;
|
||||
if (samples_to_copy < 0 || samples_in_stream < samples_to_copy)
|
||||
samples_to_copy = samples_in_stream;
|
||||
}
|
||||
@ -320,7 +319,7 @@ static void decode_mpeg_custom(VGMSTREAM* vgmstream, mpeg_codec_data* data, samp
|
||||
if (samples_to_discard > data->samples_to_discard)
|
||||
samples_to_discard = data->samples_to_discard;
|
||||
|
||||
for (i = 0; i < data->streams_size; i++) {
|
||||
for (int i = 0; i < data->streams_size; i++) {
|
||||
data->streams[i].samples_used += samples_to_discard;
|
||||
}
|
||||
data->samples_to_discard -= samples_to_discard;
|
||||
@ -329,24 +328,21 @@ static void decode_mpeg_custom(VGMSTREAM* vgmstream, mpeg_codec_data* data, samp
|
||||
|
||||
/* mux streams channels (1/2ch combos) to outbuf (Nch) */
|
||||
if (samples_to_copy > 0) {
|
||||
int ch, stream;
|
||||
|
||||
if (samples_to_copy > samples_to_do - samples_done)
|
||||
samples_to_copy = samples_to_do - samples_done;
|
||||
|
||||
ch = 0;
|
||||
for (stream = 0; stream < data->streams_size; stream++) {
|
||||
int ch = 0;
|
||||
for (int stream = 0; stream < data->streams_size; stream++) {
|
||||
mpeg_custom_stream* ms = &data->streams[stream];
|
||||
sample_t* inbuf = (sample_t *)ms->output_buffer;
|
||||
sample_t* sbuf = ms->sbuf;
|
||||
int stream_channels = ms->channels_per_frame;
|
||||
int stream_ch, s;
|
||||
|
||||
for (stream_ch = 0; stream_ch < stream_channels; stream_ch++) {
|
||||
for (s = 0; s < samples_to_copy; s++) {
|
||||
for (int stream_ch = 0; stream_ch < stream_channels; stream_ch++) {
|
||||
for (int s = 0; s < samples_to_copy; s++) {
|
||||
size_t stream_sample = (ms->samples_used+s)*stream_channels + stream_ch;
|
||||
size_t buffer_sample = (samples_done+s)*channels + ch;
|
||||
|
||||
outbuf[buffer_sample] = inbuf[stream_sample];
|
||||
outbuf[buffer_sample] = sbuf[stream_sample];
|
||||
}
|
||||
ch++;
|
||||
}
|
||||
@ -361,7 +357,7 @@ static void decode_mpeg_custom(VGMSTREAM* vgmstream, mpeg_codec_data* data, samp
|
||||
|
||||
/* Handle offsets depending on the data layout (may only use half VGMSTREAMCHANNELs with 2ch streams)
|
||||
* With multiple offsets they should already start in the first frame of each stream. */
|
||||
for (i=0; i < data->streams_size; i++) {
|
||||
for (int i = 0; i < data->streams_size; i++) {
|
||||
switch(data->type) {
|
||||
//case MPEG_FSB:
|
||||
/* same offset: alternate frames between streams (maybe needed for weird layouts?) */
|
||||
@ -385,6 +381,7 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL* stream, mpeg_codec_data*
|
||||
int rc, ok;
|
||||
mpeg_custom_stream* ms = &data->streams[num_stream];
|
||||
int channels_per_frame = ms->channels_per_frame;
|
||||
uint8_t* sbuf = ms->sbuf;
|
||||
|
||||
//;VGM_LOG("MPEG: decode stream%i @ 0x%08lx (filled=%i, used=%i, buffer_full=%i)\n", num_stream, stream->offset, ms->samples_filled, ms->samples_used, ms->buffer_full);
|
||||
|
||||
@ -424,8 +421,8 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL* stream, mpeg_codec_data*
|
||||
|
||||
/* parse frame may not touch the buffer (only move offset, or fill the sample buffer) */
|
||||
if (ms->bytes_in_buffer) {
|
||||
ms->buffer_full = 1;
|
||||
ms->buffer_used = 0;
|
||||
ms->buffer_full = true;
|
||||
ms->buffer_used = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -436,26 +433,25 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL* stream, mpeg_codec_data*
|
||||
//;VGM_LOG("MPEG: feed new data and get samples\n");
|
||||
rc = mpg123_decode(ms->handle,
|
||||
ms->buffer, ms->bytes_in_buffer,
|
||||
(unsigned char*)ms->output_buffer + bytes_filled, ms->output_buffer_size - bytes_filled,
|
||||
sbuf + bytes_filled, ms->sbuf_size - bytes_filled,
|
||||
&bytes_done);
|
||||
ms->buffer_used = 1;
|
||||
ms->buffer_used = true;
|
||||
}
|
||||
else {
|
||||
//;VGM_LOG("MPEG: get samples from old data\n");
|
||||
rc = mpg123_decode(ms->handle,
|
||||
NULL, 0,
|
||||
(unsigned char*)ms->output_buffer + bytes_filled, ms->output_buffer_size - bytes_filled,
|
||||
sbuf + bytes_filled, ms->sbuf_size - bytes_filled,
|
||||
&bytes_done);
|
||||
}
|
||||
samples_filled = (bytes_done / sizeof(sample_t) / channels_per_frame);
|
||||
samples_filled = bytes_done / channels_per_frame / sizeof(sample_t);
|
||||
|
||||
/* discard for weird features (EALayer3 and PCM blocks, AWC and repeated frames) */
|
||||
if (ms->decode_to_discard) {
|
||||
size_t bytes_to_discard = 0;
|
||||
size_t decode_to_discard = ms->decode_to_discard;
|
||||
if (decode_to_discard > samples_filled)
|
||||
decode_to_discard = samples_filled;
|
||||
bytes_to_discard = sizeof(sample_t) * decode_to_discard * channels_per_frame;
|
||||
size_t bytes_to_discard = sizeof(sample_t) * decode_to_discard * channels_per_frame;
|
||||
|
||||
bytes_done -= bytes_to_discard;
|
||||
ms->decode_to_discard -= decode_to_discard;
|
||||
@ -469,7 +465,7 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL* stream, mpeg_codec_data*
|
||||
* (but only with empty mpg123 buffer, EA blocks wait for all samples decoded before advancing blocks) */
|
||||
if (!bytes_done && rc == MPG123_NEED_MORE) {
|
||||
//;VGM_LOG("MPEG: need more raw data to get samples (bytes_done=%x)\n", bytes_done);
|
||||
ms->buffer_full = 0;
|
||||
ms->buffer_full = false;
|
||||
}
|
||||
|
||||
|
||||
@ -479,8 +475,8 @@ static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL* stream, mpeg_codec_data*
|
||||
decode_fail:
|
||||
/* 0-fill but continue with other streams */
|
||||
bytes_filled = ms->samples_filled * channels_per_frame * sizeof(sample_t);
|
||||
memset(ms->output_buffer + bytes_filled, 0, ms->output_buffer_size - bytes_filled);
|
||||
ms->samples_filled = (ms->output_buffer_size / channels_per_frame / sizeof(sample_t));
|
||||
memset(sbuf + bytes_filled, 0, ms->sbuf_size - bytes_filled);
|
||||
ms->samples_filled = (ms->sbuf_size / channels_per_frame / sizeof(sample_t));
|
||||
}
|
||||
|
||||
|
||||
@ -503,7 +499,7 @@ void free_mpeg(mpeg_codec_data* data) {
|
||||
continue;
|
||||
mpg123_delete(data->streams[i].handle);
|
||||
free(data->streams[i].buffer);
|
||||
free(data->streams[i].output_buffer);
|
||||
free(data->streams[i].sbuf);
|
||||
}
|
||||
free(data->streams);
|
||||
}
|
||||
@ -586,9 +582,8 @@ static void flush_mpeg(mpeg_codec_data* data, int is_loop) {
|
||||
mpg123_open_feed(data->m); /* mpg123_feedseek won't work */
|
||||
}
|
||||
else {
|
||||
int i;
|
||||
/* re-start from 0 */
|
||||
for (i=0; i < data->streams_size; i++) {
|
||||
for (int i = 0; i < data->streams_size; i++) {
|
||||
if (!data->streams)
|
||||
continue;
|
||||
|
||||
@ -597,8 +592,8 @@ static void flush_mpeg(mpeg_codec_data* data, int is_loop) {
|
||||
if (is_loop && data->custom && !(data->type == MPEG_FSB))
|
||||
mpg123_open_feed(data->streams[i].handle);
|
||||
data->streams[i].bytes_in_buffer = 0;
|
||||
data->streams[i].buffer_full = 0;
|
||||
data->streams[i].buffer_used = 0;
|
||||
data->streams[i].buffer_full = false;
|
||||
data->streams[i].buffer_used = false;
|
||||
data->streams[i].samples_filled = 0;
|
||||
data->streams[i].samples_used = 0;
|
||||
data->streams[i].current_size_count = 0;
|
||||
@ -610,8 +605,8 @@ static void flush_mpeg(mpeg_codec_data* data, int is_loop) {
|
||||
}
|
||||
|
||||
data->bytes_in_buffer = 0;
|
||||
data->buffer_full = 0;
|
||||
data->buffer_used = 0;
|
||||
data->buffer_full = false;
|
||||
data->buffer_used = false;
|
||||
}
|
||||
|
||||
int mpeg_get_sample_rate(mpeg_codec_data* data) {
|
||||
|
@ -12,16 +12,16 @@
|
||||
/* represents a single MPEG stream */
|
||||
typedef struct {
|
||||
/* per stream as sometimes mpg123 must be fed in passes if data is big enough (ex. EALayer3 multichannel) */
|
||||
uint8_t *buffer; /* raw data buffer */
|
||||
uint8_t* buffer; /* raw data buffer */
|
||||
size_t buffer_size;
|
||||
size_t bytes_in_buffer;
|
||||
int buffer_full; /* raw buffer has been filled */
|
||||
int buffer_used; /* raw buffer has been fed to the decoder */
|
||||
bool buffer_full; /* raw buffer has been filled */
|
||||
bool buffer_used; /* raw buffer has been fed to the decoder */
|
||||
|
||||
mpg123_handle* handle; /* MPEG decoder */
|
||||
|
||||
uint8_t *output_buffer; /* decoded samples from this stream (in bytes for mpg123) */
|
||||
size_t output_buffer_size;
|
||||
void* sbuf; /* decoded samples from this stream */
|
||||
size_t sbuf_size; /* in bytes for mpg123 */
|
||||
size_t samples_filled; /* data in the buffer (in samples) */
|
||||
size_t samples_used; /* data extracted from the buffer */
|
||||
|
||||
@ -37,9 +37,10 @@ struct mpeg_codec_data {
|
||||
uint8_t* buffer; /* raw data buffer */
|
||||
size_t buffer_size;
|
||||
size_t bytes_in_buffer;
|
||||
int buffer_full; /* raw buffer has been filled */
|
||||
int buffer_used; /* raw buffer has been fed to the decoder */
|
||||
mpg123_handle *m; /* MPEG decoder */
|
||||
bool buffer_full; /* raw buffer has been filled */
|
||||
bool buffer_used; /* raw buffer has been fed to the decoder */
|
||||
|
||||
mpg123_handle* m; /* MPEG decoder */
|
||||
struct mpg123_frameinfo mi; /* start info, so it's available even when resetting */
|
||||
|
||||
/* for internal use */
|
||||
|
@ -454,6 +454,7 @@ static const char* extension_list[] = {
|
||||
"rsnd", //txth/reserved [Birushana: Ichijuu no Kaze (Switch)]
|
||||
"rsp",
|
||||
"rstm", //fake extension/header id for .rstm (in bigfiles)
|
||||
"rvw", //txth/reserved [Half-Minute Hero (PC)]
|
||||
"rvws",
|
||||
"rwar",
|
||||
"rwav",
|
||||
|
@ -92,7 +92,7 @@ void render_vgmstream_blocked(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
|
||||
|
||||
return;
|
||||
decode_fail:
|
||||
sbuf_silence(outbuf, sample_count, vgmstream->channels, samples_filled);
|
||||
sbuf_silence_s16(outbuf, sample_count, vgmstream->channels, samples_filled);
|
||||
}
|
||||
|
||||
/* helper functions to parse new block */
|
||||
|
@ -38,5 +38,5 @@ void render_vgmstream_flat(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vg
|
||||
|
||||
return;
|
||||
decode_fail:
|
||||
sbuf_silence(outbuf, sample_count, vgmstream->channels, samples_filled);
|
||||
sbuf_silence_s16(outbuf, sample_count, vgmstream->channels, samples_filled);
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ void render_vgmstream_interleave(sample_t* outbuf, int32_t sample_count, VGMSTRE
|
||||
layout_config_t layout = {0};
|
||||
if (!setup_helper(&layout, vgmstream)) {
|
||||
VGM_LOG_ONCE("INTERLEAVE: wrong config found\n");
|
||||
sbuf_silence(outbuf, sample_count, vgmstream->channels, 0);
|
||||
sbuf_silence_s16(outbuf, sample_count, vgmstream->channels, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -193,5 +193,5 @@ void render_vgmstream_interleave(sample_t* outbuf, int32_t sample_count, VGMSTRE
|
||||
|
||||
return;
|
||||
decode_fail:
|
||||
sbuf_silence(outbuf, sample_count, vgmstream->channels, samples_filled);
|
||||
sbuf_silence_s16(outbuf, sample_count, vgmstream->channels, samples_filled);
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
|
||||
|
||||
return;
|
||||
decode_fail:
|
||||
sbuf_silence(outbuf, sample_count, data->output_channels, samples_filled);
|
||||
sbuf_silence_s16(outbuf, sample_count, data->output_channels, samples_filled);
|
||||
}
|
||||
|
||||
|
||||
|
@ -23,7 +23,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
|
||||
if (data->current_segment >= data->segment_count) {
|
||||
VGM_LOG_ONCE("SEGMENT: wrong current segment\n");
|
||||
sbuf_silence(outbuf, sample_count, data->output_channels, 0);
|
||||
sbuf_silence_s16(outbuf, sample_count, data->output_channels, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
render_vgmstream(buf, samples_to_do, data->segments[data->current_segment]);
|
||||
|
||||
if (use_internal_buffer) {
|
||||
sbuf_copy_samples(outbuf, data->output_channels, data->buffer, current_channels, samples_to_do, samples_filled);
|
||||
sbuf_copy_segments(outbuf, data->output_channels, data->buffer, current_channels, samples_to_do, samples_filled);
|
||||
}
|
||||
|
||||
samples_filled += samples_to_do;
|
||||
@ -87,7 +87,7 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
|
||||
return;
|
||||
decode_fail:
|
||||
sbuf_silence(outbuf, sample_count, data->output_channels, samples_filled);
|
||||
sbuf_silence_s16(outbuf, sample_count, data->output_channels, samples_filled);
|
||||
}
|
||||
|
||||
|
||||
|
@ -188,6 +188,7 @@ typedef struct {
|
||||
// ** this type of downmixing is very simplistic and not recommended
|
||||
|
||||
bool force_pcm16; // forces output buffer to be remixed into PCM16
|
||||
bool force_float; // forces output buffer to be remixed into float
|
||||
|
||||
} libvgmstream_config_t;
|
||||
|
||||
@ -201,7 +202,7 @@ LIBVGMSTREAM_API void libvgmstream_setup(libvgmstream_t* lib, libvgmstream_confi
|
||||
|
||||
/* configures how vgmstream opens the format */
|
||||
typedef struct {
|
||||
libvgmstream_streamfile_t* libsf; // custom IO streamfile that provides reader info for vgmstream
|
||||
libstreamfile_t* libsf; // custom IO streamfile that provides reader info for vgmstream
|
||||
// ** not needed after _open and should be closed, as vgmstream re-opens its own SFs internally as needed
|
||||
|
||||
int subsong_index; // target subsong (1..N) or 0 = default/first
|
||||
@ -345,7 +346,7 @@ typedef struct {
|
||||
* - libsf should point to a !tags.m3u file
|
||||
* - unlike libvgmstream_open, sf tagfile must be valid during the tag extraction process.
|
||||
*/
|
||||
LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libvgmstream_streamfile_t* libsf);
|
||||
LIBVGMSTREAM_API libvgmstream_tags_t* libvgmstream_tags_init(libstreamfile_t* libsf);
|
||||
|
||||
/* Finds tags for a new filename. Must be called first before extracting tags.
|
||||
*/
|
||||
|
@ -12,14 +12,15 @@
|
||||
|
||||
|
||||
enum {
|
||||
LIBVGMSTREAM_STREAMFILE_SEEK_SET = 0,
|
||||
LIBVGMSTREAM_STREAMFILE_SEEK_CUR = 1,
|
||||
LIBVGMSTREAM_STREAMFILE_SEEK_END = 2,
|
||||
//LIBVGMSTREAM_STREAMFILE_SEEK_GET_OFFSET = 3,
|
||||
//LIBVGMSTREAM_STREAMFILE_SEEK_GET_SIZE = 5,
|
||||
LIBSTREAMFILE_SEEK_SET = 0,
|
||||
LIBSTREAMFILE_SEEK_CUR = 1,
|
||||
LIBSTREAMFILE_SEEK_END = 2,
|
||||
//LIBSTREAMFILE_SEEK_GET_OFFSET = 3,
|
||||
//LIBSTREAMFILE_SEEK_GET_SIZE = 5,
|
||||
};
|
||||
|
||||
typedef struct libvgmstream_streamfile_t {
|
||||
// maybe "libvgmstream_streamfile_t" but it was getting unwieldly
|
||||
typedef struct libstreamfile_t {
|
||||
//uint32_t flags; // info flags for vgmstream
|
||||
void* user_data; // any internal structure
|
||||
|
||||
@ -44,23 +45,23 @@ typedef struct libvgmstream_streamfile_t {
|
||||
/* open another streamfile from filename (may be some path/protocol, or same as current get_name = reopen)
|
||||
* - vgmstream opens stuff based on current get_name (relative), so there shouldn't be need to transform this path
|
||||
*/
|
||||
struct libvgmstream_streamfile_t* (*open)(void* user_data, const char* filename);
|
||||
struct libstreamfile_t* (*open)(void* user_data, const char* filename);
|
||||
|
||||
/* free current SF (needed for copied streamfiles) */
|
||||
void (*close)(struct libvgmstream_streamfile_t* libsf);
|
||||
void (*close)(struct libstreamfile_t* libsf);
|
||||
|
||||
} libvgmstream_streamfile_t;
|
||||
} libstreamfile_t;
|
||||
|
||||
|
||||
/* helper */
|
||||
static inline void libvgmstream_streamfile_close(libvgmstream_streamfile_t* libsf) {
|
||||
static inline void libstreamfile_close(libstreamfile_t* libsf) {
|
||||
if (!libsf || !libsf->close)
|
||||
return;
|
||||
libsf->close(libsf);
|
||||
}
|
||||
|
||||
|
||||
LIBVGMSTREAM_API libvgmstream_streamfile_t* libvgmstream_streamfile_open_from_stdio(const char* filename);
|
||||
LIBVGMSTREAM_API libstreamfile_t* libstreamfile_open_from_stdio(const char* filename);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
138
src/meta/psb.c
138
src/meta/psb.c
@ -48,17 +48,18 @@ typedef struct {
|
||||
int32_t num_samples;
|
||||
int32_t body_samples;
|
||||
int32_t intro_samples;
|
||||
int32_t skip_samples;
|
||||
int32_t intro_skip;
|
||||
int32_t body_skip;
|
||||
int loop_flag;
|
||||
int loop_range;
|
||||
bool loop_range;
|
||||
int32_t loop_start;
|
||||
int32_t loop_end;
|
||||
int loop_test;
|
||||
bool loop_type_unknown;
|
||||
|
||||
} psb_header_t;
|
||||
|
||||
|
||||
static int parse_psb(STREAMFILE* sf, psb_header_t* psb);
|
||||
static bool parse_psb(STREAMFILE* sf, psb_header_t* psb);
|
||||
|
||||
|
||||
static segmented_layout_data* build_segmented_psb_opus(STREAMFILE* sf, psb_header_t* psb);
|
||||
@ -244,7 +245,7 @@ VGMSTREAM* init_vgmstream_psb(STREAMFILE* sf) {
|
||||
* - loop_start + loop_length [LoM (PC/And), Namco Museum V1 (PC), Senxin Aleste (PC)]
|
||||
* - loop_start + loop_end [G-Darius (Sw)]
|
||||
* (only in some cases of "loop" field so shouldn't happen to often) */
|
||||
if (psb.loop_test) {
|
||||
if (psb.loop_type_unknown) {
|
||||
if (psb.loop_start + psb.loop_end <= vgmstream->num_samples) {
|
||||
vgmstream->loop_end_sample += psb.loop_start;
|
||||
/* assumed, matches num_samples in LoM and Namco but not in Senjin Aleste (unknown in G-Darius) */
|
||||
@ -266,30 +267,33 @@ fail:
|
||||
|
||||
static segmented_layout_data* build_segmented_psb_opus(STREAMFILE* sf, psb_header_t* psb) {
|
||||
segmented_layout_data* data = NULL;
|
||||
int i, pos = 0, segment_count = 0, max_count = 2;
|
||||
|
||||
//TODO improve
|
||||
//TODO these use standard switch opus (VBR), could sub-file? but skip_samples becomes more complex
|
||||
|
||||
uint32_t offsets[] = {psb->intro_offset, psb->body_offset};
|
||||
uint32_t sizes[] = {psb->intro_size, psb->body_size};
|
||||
uint32_t samples[] = {psb->intro_samples, psb->body_samples};
|
||||
uint32_t skips[] = {0, psb->skip_samples};
|
||||
int32_t samples[] = {psb->intro_samples, psb->body_samples};
|
||||
int32_t skips[] = {0, psb->body_skip};
|
||||
|
||||
int pos = 0, max_count = 2;
|
||||
|
||||
/* intro + body (looped songs) or just body (standard songs)
|
||||
* In full loops intro is 0 samples with a micro 1-frame opus [Nekopara (Switch)] */
|
||||
if (offsets[0] && samples[0])
|
||||
int segment_count = 0;
|
||||
if (offsets[0] && samples[0] > 0)
|
||||
segment_count++;
|
||||
if (offsets[1] && samples[1])
|
||||
if (offsets[1] && samples[1] > 0)
|
||||
segment_count++;
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_segmented(segment_count);
|
||||
if (!data) goto fail;
|
||||
|
||||
for (i = 0; i < max_count; i++) {
|
||||
if (!offsets[i] || !samples[i])
|
||||
for (int i = 0; i < max_count; i++) {
|
||||
if (!offsets[i] || samples[i] <= 0)
|
||||
continue;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
int start = read_u32le(offsets[i] + 0x10, sf) + 0x08;
|
||||
@ -337,14 +341,13 @@ fail:
|
||||
|
||||
static layered_layout_data* build_layered_psb(STREAMFILE* sf, psb_header_t* psb) {
|
||||
layered_layout_data* data = NULL;
|
||||
int i;
|
||||
|
||||
|
||||
/* init layout */
|
||||
data = init_layout_layered(psb->layers);
|
||||
if (!data) goto fail;
|
||||
|
||||
for (i = 0; i < psb->layers; i++) {
|
||||
for (int i = 0; i < psb->layers; i++) {
|
||||
switch (psb->codec) {
|
||||
case PCM: {
|
||||
VGMSTREAM* v = allocate_vgmstream(1, 0);
|
||||
@ -392,7 +395,7 @@ fail:
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
static int prepare_fmt(STREAMFILE* sf, psb_header_t* psb) {
|
||||
static bool prepare_fmt(STREAMFILE* sf, psb_header_t* psb) {
|
||||
uint32_t offset = psb->fmt_offset;
|
||||
if (!offset)
|
||||
return 1; /* other codec, probably */
|
||||
@ -429,12 +432,12 @@ static int prepare_fmt(STREAMFILE* sf, psb_header_t* psb) {
|
||||
|
||||
}
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
fail:
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) {
|
||||
static bool prepare_codec(STREAMFILE* sf, psb_header_t* psb) {
|
||||
const char* spec = psb->tmp->spec;
|
||||
const char* ext = psb->tmp->ext;
|
||||
|
||||
@ -456,7 +459,7 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) {
|
||||
default:
|
||||
goto fail;
|
||||
}
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* try console strings */
|
||||
@ -471,23 +474,28 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) {
|
||||
if (strcmp(ext, ".opus") == 0) {
|
||||
psb->codec = OPUSNX;
|
||||
|
||||
psb->body_samples -= psb->skip_samples;
|
||||
psb->body_samples -= psb->body_skip;
|
||||
|
||||
/* When setting loopstr="range:N,M", doesn't seem to transition properly (clicks) unless aligned (not always?)
|
||||
* > N=intro's sampleCount, M=intro+body's sampleCount - skipSamples - default_skip, but not always
|
||||
* [Anonymous;Code (Switch)-bgm08, B-Project: Ryuusei Fantasia (Switch)-bgm27] */
|
||||
if (psb->loop_range) {
|
||||
//TODO read actual default skip
|
||||
psb->intro_samples -= 120;
|
||||
psb->body_samples -= 120;
|
||||
/* Sometimes intro + body loops don't seem to transition properly (clicks) unless aligned, but not always
|
||||
* > N=intro's sampleCount, M=intro+body's sampleCount - skipSamples - default_skip. [B-Project: Ryuusei Fantasia (Switch)-bgm27]
|
||||
* However this click may happen too in other codecs, so it's probably an encoder quirk [Anonymous;Code (Switch)-bgm08 SW opus vs PC msadpcm]
|
||||
* There is a 'loopstr="range:N,M"' value, which may match intro + body samples, or be slightly smaller than body samples.
|
||||
* Since presumably the point of separate intro + body is manual looping via subfiles, assume loopstr is just info and not used.
|
||||
* skipSamples may not be set with full loops [The Quintessential Quintuplets: Memories of a Quintessential Summer (Switch)-bgm02 vs bgm03] */
|
||||
#if 0
|
||||
if (psb->body_skip) {
|
||||
int default_skip = 120; //TODO read actual value, but harder to fix loops later
|
||||
if (psb->intro_samples > default_skip) psb->intro_samples -= default_skip; //this seems to match loop_start plus may be 0
|
||||
if (psb->body_samples > default_skip) psb->body_samples -= default_skip;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!psb->loop_flag)
|
||||
psb->loop_flag = psb->intro_samples > 0;
|
||||
psb->loop_start = psb->intro_samples;
|
||||
psb->loop_end = psb->body_samples + psb->intro_samples;
|
||||
psb->num_samples = psb->intro_samples + psb->body_samples;
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Legend of Mana (Switch), layered */
|
||||
@ -495,7 +503,7 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) {
|
||||
psb->codec = DSP;
|
||||
|
||||
psb->channels = psb->layers;
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Castlevania Advance Collection (Switch), layered */
|
||||
@ -504,13 +512,13 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) {
|
||||
psb->bps = 16;
|
||||
|
||||
psb->channels = psb->layers;
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(spec, "ps3") == 0) {
|
||||
psb->codec = RIFF_AT3;
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(spec, "vita") == 0 || strcmp(spec, "ps4") == 0) {
|
||||
@ -518,7 +526,7 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) {
|
||||
psb->codec = RIFF_AT9;
|
||||
else
|
||||
psb->codec = VAG;
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(spec, "and") == 0) {
|
||||
@ -527,29 +535,29 @@ static int prepare_codec(STREAMFILE* sf, psb_header_t* psb) {
|
||||
|
||||
if (strcmp(ext, ".ogg") == 0) {
|
||||
psb->codec = OGG_VORBIS;
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(ext, ".wav") == 0) {
|
||||
psb->codec = RIFF_WAV;
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
vgm_logi("PSB: unknown codec (report)\n");
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static int prepare_name(psb_header_t* psb) {
|
||||
static bool prepare_name(psb_header_t* psb) {
|
||||
const char* main_name = psb->tmp->voice;
|
||||
const char* sub_name = psb->tmp->uniq;
|
||||
char* buf = psb->readable_name;
|
||||
int buf_size = sizeof(psb->readable_name);
|
||||
|
||||
if (!main_name) /* shouldn't happen */
|
||||
return 1;
|
||||
return true;
|
||||
|
||||
if (!sub_name)
|
||||
sub_name = psb->tmp->wav;
|
||||
@ -575,19 +583,19 @@ static int prepare_name(psb_header_t* psb) {
|
||||
snprintf(buf, buf_size, "%s", main_name);
|
||||
}
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int prepare_psb_extra(STREAMFILE* sf, psb_header_t* psb) {
|
||||
static bool prepare_psb_extra(STREAMFILE* sf, psb_header_t* psb) {
|
||||
if (!prepare_fmt(sf, psb))
|
||||
goto fail;
|
||||
if (!prepare_codec(sf, psb))
|
||||
goto fail;
|
||||
if (!prepare_name(psb))
|
||||
goto fail;
|
||||
return 1;
|
||||
return true;
|
||||
fail:
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -600,15 +608,14 @@ fail:
|
||||
* - data/dpds/fmt/wav/loop
|
||||
* - pan: array [N.0 .. 0.N] (when N layers, in practice just a wonky L/R definition)
|
||||
*/
|
||||
static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) {
|
||||
int i;
|
||||
static bool parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) {
|
||||
psb_node_t nchan, narch, nsub, node;
|
||||
|
||||
psb->layers = psb_node_get_count(nchans);
|
||||
if (psb->layers == 0) goto fail;
|
||||
if (psb->layers > PSB_MAX_LAYERS) goto fail;
|
||||
|
||||
for (i = 0; i < psb->layers; i++) {
|
||||
for (int i = 0; i < psb->layers; i++) {
|
||||
psb_data_t data;
|
||||
psb_type_t type;
|
||||
|
||||
@ -664,7 +671,7 @@ static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) {
|
||||
psb->loop_end = le.num;
|
||||
}
|
||||
|
||||
psb->loop_test = 1; /* loop end meaning varies*/
|
||||
psb->loop_type_unknown = true; /* loop end meaning varies*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -673,7 +680,7 @@ static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) {
|
||||
psb->body_offset = data.offset;
|
||||
psb->body_size = data.size;
|
||||
psb->body_samples = psb_node_get_integer(&node, "sampleCount");
|
||||
psb->skip_samples = psb_node_get_integer(&node, "skipSampleCount"); /* fixed to seek_preroll? (80ms) */
|
||||
psb->body_skip = psb_node_get_integer(&node, "skipSampleCount"); /* fixed to seek_preroll? (80ms) */
|
||||
}
|
||||
|
||||
if (psb_node_by_key(&narch, "intro", &node)) {
|
||||
@ -681,6 +688,8 @@ static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) {
|
||||
psb->intro_offset = data.offset;
|
||||
psb->intro_size = data.size;
|
||||
psb->intro_samples = psb_node_get_integer(&node, "sampleCount");
|
||||
psb->intro_skip = psb_node_get_integer(&node, "skipSampleCount"); /* fixed to seek_preroll? (80ms) */
|
||||
vgm_asserti(psb->intro_skip, "PSB: intro skip found\n");
|
||||
}
|
||||
|
||||
data = psb_node_get_data(&narch, "dpds");
|
||||
@ -714,15 +723,16 @@ static int parse_psb_channels(psb_header_t* psb, psb_node_t* nchans) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
|
||||
return true;
|
||||
fail:
|
||||
VGM_LOG("psb: can't parse channel\n");
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* parse a single archive, that can contain extra info here or inside channels */
|
||||
static int parse_psb_voice(psb_header_t* psb, psb_node_t* nvoice) {
|
||||
static bool parse_psb_voice(psb_header_t* psb, psb_node_t* nvoice) {
|
||||
psb_node_t nsong, nchans;
|
||||
|
||||
|
||||
@ -757,7 +767,7 @@ static int parse_psb_voice(psb_header_t* psb, psb_node_t* nvoice) {
|
||||
/* loopstr values:
|
||||
* - "none", w/ loop=0
|
||||
* - "all", w/ loop = 2 [Legend of Mana (multi)]
|
||||
* - "range:N,M", w/ loop = 2 [Anonymous;Code (Switch)] */
|
||||
* - "range:N,M", w/ loop = 2 [Anonymous;Code (Switch/PC)], assumed to be info info */
|
||||
psb->loop_range = loopstr && strncmp(loopstr, "range:", 6) == 0; /* slightly different in rare cases */
|
||||
}
|
||||
|
||||
@ -769,10 +779,10 @@ static int parse_psb_voice(psb_header_t* psb, psb_node_t* nvoice) {
|
||||
* - group?
|
||||
*/
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
fail:
|
||||
VGM_LOG("psb: can't parse voice\n");
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* .psb is binary JSON-like structure that can be used to hold various formats, we want audio data:
|
||||
@ -790,7 +800,7 @@ fail:
|
||||
* From decompilations, audio code reads common keys up to "archData", then depends on game (not unified).
|
||||
* Keys are (seemingly) stored in text order.
|
||||
*/
|
||||
static int parse_psb(STREAMFILE* sf, psb_header_t* psb) {
|
||||
static bool parse_psb(STREAMFILE* sf, psb_header_t* psb) {
|
||||
psb_temp_t tmp = {0};
|
||||
psb_context_t* ctx = NULL;
|
||||
psb_node_t nroot, nvoice;
|
||||
@ -801,7 +811,7 @@ static int parse_psb(STREAMFILE* sf, psb_header_t* psb) {
|
||||
|
||||
ctx = psb_init(sf);
|
||||
if (!ctx) goto fail;
|
||||
//psb_print(ctx);
|
||||
psb_print(ctx);
|
||||
|
||||
/* main process */
|
||||
psb_get_root(ctx, &nroot);
|
||||
@ -838,25 +848,9 @@ static int parse_psb(STREAMFILE* sf, psb_header_t* psb) {
|
||||
|
||||
psb->tmp = NULL;
|
||||
psb_close(ctx);
|
||||
return 1;
|
||||
return true;
|
||||
fail:
|
||||
psb_close(ctx);
|
||||
VGM_LOG("psb: can't parse PSB\n");
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0
|
||||
typedef struct {
|
||||
void* init;
|
||||
const char* id32;
|
||||
const char* exts;
|
||||
} metadef_t;
|
||||
|
||||
metadef_t md_psb = {
|
||||
.init = init_vgmstream_psb,
|
||||
.exts = "psb",
|
||||
.id32 = "PSB\0", //24b/masked IDs?
|
||||
.id32 = get_id32be("PSB\0"), //???
|
||||
.idfn = psb_check_id,
|
||||
}
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user