mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-01-21 08:43:40 +01:00
commit
5682f3b9cc
24
doc/TXTP.md
24
doc/TXTP.md
@ -235,6 +235,30 @@ loop_mode = keep
|
||||
```
|
||||
|
||||
|
||||
### Silent files
|
||||
You can put `?.` in an entry to make a silent (non-existing) file. By default takes channels and sample rate of nearby files, can be combined with regular commands to configure.
|
||||
```
|
||||
intro.adx
|
||||
?.silence #b 3.0 # 3 seconds of silence
|
||||
loop.adx
|
||||
```
|
||||
|
||||
It also doubles as a quick "silence this file" while keeping the same structure, for complex cases. The `.` can actually be anywhere after `?`, but must appear before commands to function correctly.
|
||||
```
|
||||
layer1a.adx
|
||||
?layer1b.adx
|
||||
group = -L2
|
||||
|
||||
?layer2a.adx
|
||||
layer2b.adx
|
||||
group = -L2
|
||||
|
||||
group = -S2
|
||||
```
|
||||
|
||||
Most of the time you can do the same with `#p`/`#P` padding commands or `#@volume 0.0`. This is mainly for complex engines that combine silent entries in twisted ways. You can't silence `group` with `?group` though since they aren't considered "entries".
|
||||
|
||||
|
||||
## TXTP COMMANDS
|
||||
You can set file commands by adding multiple `#(command)` after the name. `#(space)(anything)` is considered a comment and ignored, as well as any command not understood.
|
||||
|
||||
|
12
src/decode.c
12
src/decode.c
@ -293,6 +293,9 @@ int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream) {
|
||||
* (or some internal sample buffer max too). */
|
||||
|
||||
switch (vgmstream->coding_type) {
|
||||
case coding_SILENCE:
|
||||
return 0;
|
||||
|
||||
case coding_CRI_ADX:
|
||||
case coding_CRI_ADX_fixed:
|
||||
case coding_CRI_ADX_exp:
|
||||
@ -495,6 +498,9 @@ int get_vgmstream_samples_per_frame(VGMSTREAM* vgmstream) {
|
||||
/* Get the number of bytes of a single frame (smallest self-contained byte group, 1/N channels) */
|
||||
int get_vgmstream_frame_size(VGMSTREAM* vgmstream) {
|
||||
switch (vgmstream->coding_type) {
|
||||
case coding_SILENCE:
|
||||
return 0;
|
||||
|
||||
case coding_CRI_ADX:
|
||||
case coding_CRI_ADX_fixed:
|
||||
case coding_CRI_ADX_exp:
|
||||
@ -695,7 +701,7 @@ int get_vgmstream_shortframe_size(VGMSTREAM* vgmstream) {
|
||||
|
||||
/* Decode samples into the buffer. Assume that we have written samples_written into the
|
||||
* buffer already, and we have samples_to_do consecutive samples ahead of us (won't call
|
||||
* more than one frame it configured above to do so).
|
||||
* more than one frame if configured above to do so).
|
||||
* Called by layouts since they handle samples written/to_do */
|
||||
void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_do, sample_t* buffer) {
|
||||
int ch;
|
||||
@ -703,6 +709,10 @@ void decode_vgmstream(VGMSTREAM* vgmstream, int samples_written, int samples_to_
|
||||
buffer += samples_written * vgmstream->channels; /* passed externally to simplify I guess */
|
||||
|
||||
switch (vgmstream->coding_type) {
|
||||
case coding_SILENCE:
|
||||
memset(buffer, 0, samples_to_do * vgmstream->channels * sizeof(sample_t));
|
||||
break;
|
||||
|
||||
case coding_CRI_ADX:
|
||||
case coding_CRI_ADX_exp:
|
||||
case coding_CRI_ADX_fixed:
|
||||
|
@ -670,6 +670,8 @@ typedef struct {
|
||||
|
||||
|
||||
static const coding_info coding_info_list[] = {
|
||||
{coding_SILENCE, "Silence"},
|
||||
|
||||
{coding_PCM16LE, "Little Endian 16-bit PCM"},
|
||||
{coding_PCM16BE, "Big Endian 16-bit PCM"},
|
||||
{coding_PCM16_int, "16-bit PCM with 2 byte interleave (block)"},
|
||||
@ -868,6 +870,7 @@ static const layout_info layout_info_list[] = {
|
||||
};
|
||||
|
||||
static const meta_info meta_info_list[] = {
|
||||
{meta_SILENCE, "Silence"},
|
||||
{meta_RSTM, "Nintendo RSTM header"},
|
||||
{meta_STRM, "Nintendo STRM header"},
|
||||
{meta_ADX_03, "CRI ADX header type 03"},
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
/* Decodes samples for flat streams.
|
||||
* Data forms a single stream, and the decoder may internally skip chunks and move offsets as needed. */
|
||||
void render_vgmstream_flat(sample_t* buffer, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
void render_vgmstream_flat(sample_t* outbuf, int32_t sample_count, VGMSTREAM* vgmstream) {
|
||||
int samples_written = 0;
|
||||
int samples_per_frame, samples_this_block;
|
||||
|
||||
@ -25,17 +25,19 @@ void render_vgmstream_flat(sample_t* buffer, int32_t sample_count, VGMSTREAM* vg
|
||||
if (samples_to_do > sample_count - samples_written)
|
||||
samples_to_do = sample_count - samples_written;
|
||||
|
||||
if (samples_to_do == 0) {
|
||||
VGM_LOG("layout_flat: wrong samples_to_do 0 found\n"); /* could happen when calling render at EOF? */
|
||||
//VGM_LOG("layout_flat: tb=%i sib=%i, spf=%i\n", samples_this_block, vgmstream->samples_into_block, samples_per_frame);
|
||||
memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t));
|
||||
break;
|
||||
if (samples_to_do == 0) { /* when decoding more than num_samples */
|
||||
VGM_LOG("FLAT: samples_to_do 0\n");
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
decode_vgmstream(vgmstream, samples_written, samples_to_do, buffer);
|
||||
decode_vgmstream(vgmstream, samples_written, samples_to_do, outbuf);
|
||||
|
||||
samples_written += samples_to_do;
|
||||
vgmstream->current_sample += samples_to_do;
|
||||
vgmstream->samples_into_block += samples_to_do;
|
||||
}
|
||||
|
||||
return;
|
||||
decode_fail:
|
||||
memset(outbuf + samples_written * vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t));
|
||||
}
|
||||
|
@ -34,6 +34,10 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
|
||||
if (samples_to_do > sample_count - samples_written)
|
||||
samples_to_do = sample_count - samples_written;
|
||||
|
||||
if (samples_to_do <= 0) { /* when decoding more than num_samples */
|
||||
VGM_LOG("LAYERED: samples_to_do 0\n");
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
/* decode all layers */
|
||||
ch = 0;
|
||||
@ -65,6 +69,10 @@ void render_vgmstream_layered(sample_t* outbuf, int32_t sample_count, VGMSTREAM*
|
||||
vgmstream->current_sample += samples_to_do;
|
||||
vgmstream->samples_into_block += samples_to_do;
|
||||
}
|
||||
|
||||
return;
|
||||
decode_fail:
|
||||
memset(outbuf + samples_written * data->output_channels, 0, (sample_count - samples_written) * data->output_channels * sizeof(sample_t));
|
||||
}
|
||||
|
||||
|
||||
|
@ -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("SEGMENT: wrong current segment\n");
|
||||
return;
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
samples_this_block = vgmstream_get_samples(data->segments[data->current_segment]);
|
||||
@ -41,10 +41,9 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
if (vgmstream->samples_into_block >= samples_this_block) {
|
||||
data->current_segment++;
|
||||
|
||||
/* could happen on last segment trying to decode more samples */
|
||||
if (data->current_segment >= data->segment_count) {
|
||||
VGM_LOG("SEGMENTED: wrong next segment\n");
|
||||
break;
|
||||
if (data->current_segment >= data->segment_count) { /* when decoding more than num_samples */
|
||||
VGM_LOG("SEGMENTED: reached last segment\n");
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
/* in case of looping spanning multiple segments */
|
||||
@ -62,9 +61,9 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
if (samples_to_do > VGMSTREAM_SEGMENT_SAMPLE_BUFFER /*&& use_internal_buffer*/) /* always for fade/etc mixes */
|
||||
samples_to_do = VGMSTREAM_SEGMENT_SAMPLE_BUFFER;
|
||||
|
||||
if (samples_to_do < 0) { /* ? */
|
||||
if (samples_to_do < 0) { /* 0 is ok? */
|
||||
VGM_LOG("SEGMENTED: wrong samples_to_do %i found\n", samples_to_do);
|
||||
break;
|
||||
goto decode_fail;
|
||||
}
|
||||
|
||||
render_vgmstream(
|
||||
@ -84,6 +83,10 @@ void render_vgmstream_segmented(sample_t* outbuf, int32_t sample_count, VGMSTREA
|
||||
vgmstream->current_sample += samples_to_do;
|
||||
vgmstream->samples_into_block += samples_to_do;
|
||||
}
|
||||
|
||||
return;
|
||||
decode_fail:
|
||||
memset(outbuf + samples_written * data->output_channels, 0, (sample_count - samples_written) * data->output_channels * sizeof(sample_t));
|
||||
}
|
||||
|
||||
void loop_layout_segmented(VGMSTREAM* vgmstream, int32_t loop_sample) {
|
||||
|
@ -1514,10 +1514,14 @@
|
||||
RelativePath=".\meta\psf.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\sgxd.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\sgxd.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\silence.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\vawx.c"
|
||||
>
|
||||
|
@ -232,6 +232,7 @@
|
||||
<ClCompile Include="meta\ps3_past.c" />
|
||||
<ClCompile Include="meta\psf.c" />
|
||||
<ClCompile Include="meta\sgxd.c" />
|
||||
<ClCompile Include="meta\silence.c" />
|
||||
<ClCompile Include="meta\sk_aud.c" />
|
||||
<ClCompile Include="meta\vawx.c" />
|
||||
<ClCompile Include="meta\seg.c" />
|
||||
|
@ -1516,6 +1516,9 @@
|
||||
<ClCompile Include="meta\sgxd.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\silence.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ngca.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
@ -115,11 +115,7 @@ VGMSTREAM* init_vgmstream_bkhd(STREAMFILE* sf) {
|
||||
|
||||
if (is_dummy || is_wmid) {
|
||||
/* for now leave a dummy song for easier .bnk index-to-subsong mapping */
|
||||
temp_sf = setup_subfile_streamfile(sf, 0x00, 1000 * 0x02 * 2, "raw");
|
||||
if (!temp_sf) goto fail;
|
||||
|
||||
//todo make some better silent entry
|
||||
vgmstream = init_vgmstream_raw_pcm(temp_sf);
|
||||
vgmstream = init_vgmstream_silence(0, 0, 0);
|
||||
if (!vgmstream) goto fail;
|
||||
}
|
||||
else {
|
||||
|
@ -66,8 +66,7 @@ VGMSTREAM* init_vgmstream_fsb_encrypted(STREAMFILE* sf) {
|
||||
vgmstream = init_vgmstream_fsb(temp_sf);
|
||||
}
|
||||
|
||||
if (vgmstream)
|
||||
dump_streamfile(temp_sf, 0);
|
||||
//;if (vgmstream) dump_streamfile(temp_sf, 0);
|
||||
|
||||
close_streamfile(temp_sf);
|
||||
if (vgmstream) break;
|
||||
|
@ -78,6 +78,9 @@ static const uint8_t key_gh5[] = { 0xFC,0xF9,0xE4,0xB3,0xF5,0x57,0x5C,0xA5,0xAC,
|
||||
/* Sekiro: Shadows Die Twice (PC) */ //"G0KTrWjS9syqF7vVD6RaVXlFD91gMgkC"
|
||||
static const uint8_t key_sek[] = { 0x47,0x30,0x4B,0x54,0x72,0x57,0x6A,0x53,0x39,0x73,0x79,0x71,0x46,0x37,0x76,0x56,0x44,0x36,0x52,0x61,0x56,0x58,0x6C,0x46,0x44,0x39,0x31,0x67,0x4D,0x67,0x6B,0x43 };
|
||||
|
||||
/* Stacking (X360) */ //"DFm3t4lFTW"
|
||||
static const uint8_t key_sta[] = { 0x44,0x46,0x6d,0x33,0x74,0x34,0x6c,0x46,0x54,0x57 };
|
||||
|
||||
// Unknown:
|
||||
// - Battle: Los Angeles
|
||||
// - Guitar Hero: Warriors of Rock, DJ hero FSB
|
||||
@ -141,6 +144,7 @@ static const fsbkey_info fsbkey_list[] = {
|
||||
{ 0,1, sizeof(key_mtj),key_mtj },// FSB3
|
||||
{ 0,1, sizeof(key_gh5),key_gh5 },// FSB4
|
||||
{ 1,0, sizeof(key_sek),key_sek },// FSB5
|
||||
{ 0,1, sizeof(key_sta),key_sta },// FSB4
|
||||
|
||||
};
|
||||
static const int fsbkey_list_count = sizeof(fsbkey_list) / sizeof(fsbkey_list[0]);
|
||||
|
@ -3,6 +3,9 @@
|
||||
|
||||
#include "../vgmstream.h"
|
||||
|
||||
VGMSTREAM* init_vgmstream_silence(int channels, int sample_rate, int32_t num_samples);
|
||||
|
||||
|
||||
VGMSTREAM * init_vgmstream_adx(STREAMFILE *streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_afc(STREAMFILE *streamFile);
|
||||
|
29
src/meta/silence.c
Normal file
29
src/meta/silence.c
Normal file
@ -0,0 +1,29 @@
|
||||
#include "meta.h"
|
||||
|
||||
/* silent stream - mainly for engines that need them or dummy subsongs */
|
||||
VGMSTREAM* init_vgmstream_silence(int channels, int sample_rate, int32_t num_samples) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
|
||||
if (channels <= 0)
|
||||
channels = 2;
|
||||
if (sample_rate <= 0)
|
||||
sample_rate = 48000;
|
||||
if (num_samples <= 0)
|
||||
num_samples = 1.0 * sample_rate;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channels, 0);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_SILENCE;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
|
||||
vgmstream->coding_type = coding_SILENCE;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
return vgmstream;
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
@ -66,6 +66,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
/* main entry */
|
||||
char filename[TXTP_LINE_MAX];
|
||||
int silent;
|
||||
|
||||
/* TXTP settings (applied at the end) */
|
||||
int range_start;
|
||||
@ -206,9 +207,46 @@ static void clean_txtp(txtp_header* txtp, int fail) {
|
||||
/* ENTRIES */
|
||||
/*******************************************************************************/
|
||||
|
||||
static int parse_silents(txtp_header* txtp) {
|
||||
int i;
|
||||
int channels = 0;
|
||||
int sample_rate = 0;
|
||||
int32_t num_samples = 0;
|
||||
|
||||
/* silents use same channels as close files */
|
||||
for (i = 0; i < txtp->vgmstream_count; i++) {
|
||||
if (!txtp->entry[i].silent) {
|
||||
channels = txtp->vgmstream[i]->channels;
|
||||
sample_rate = txtp->vgmstream[i]->sample_rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* actually open silents */
|
||||
for (i = 0; i < txtp->vgmstream_count; i++) {
|
||||
if (!txtp->entry[i].silent)
|
||||
continue;
|
||||
|
||||
txtp->vgmstream[i] = init_vgmstream_silence(channels, sample_rate, num_samples);
|
||||
if (!txtp->vgmstream[i]) goto fail;
|
||||
|
||||
apply_settings(txtp->vgmstream[i], &txtp->entry[i]);
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_silent(txtp_entry* entry) {
|
||||
/* should also contain "." in the filename for commands with seconds ("1.0") to work */
|
||||
return entry->filename[0] == '?';
|
||||
}
|
||||
|
||||
/* open all entries and apply settings to resulting VGMSTREAMs */
|
||||
static int parse_entries(txtp_header* txtp, STREAMFILE* sf) {
|
||||
int i;
|
||||
int has_silents = 0;
|
||||
|
||||
|
||||
if (txtp->entry_count == 0)
|
||||
@ -222,7 +260,16 @@ static int parse_entries(txtp_header* txtp, STREAMFILE* sf) {
|
||||
|
||||
/* open all entry files first as they'll be modified by modes */
|
||||
for (i = 0; i < txtp->vgmstream_count; i++) {
|
||||
STREAMFILE* temp_sf = open_streamfile_by_filename(sf, txtp->entry[i].filename);
|
||||
STREAMFILE* temp_sf = NULL;
|
||||
|
||||
/* silent entry ignore */
|
||||
if (is_silent(&txtp->entry[i])) {
|
||||
txtp->entry[i].silent = 1;
|
||||
has_silents = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
temp_sf = open_streamfile_by_filename(sf, txtp->entry[i].filename);
|
||||
if (!temp_sf) {
|
||||
VGM_LOG("TXTP: cannot open streamfile for %s\n", txtp->entry[i].filename);
|
||||
goto fail;
|
||||
@ -239,6 +286,11 @@ static int parse_entries(txtp_header* txtp, STREAMFILE* sf) {
|
||||
apply_settings(txtp->vgmstream[i], &txtp->entry[i]);
|
||||
}
|
||||
|
||||
if (has_silents) {
|
||||
if (!parse_silents(txtp))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 1;
|
||||
fail:
|
||||
return 0;
|
||||
|
@ -225,8 +225,9 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ww.codec == PCM || ww.codec == IMA || ww.codec == DSP || ww.codec == VORBIS || ww.codec == XMA2 || ww.codec == OPUSNX || ww.codec == OPUS) {
|
||||
ww.truncated = 1; /* only seen those, probably all exist */
|
||||
if (ww.codec == PCM || ww.codec == IMA || ww.codec == VORBIS || ww.codec == DSP || ww.codec == XMA2 ||
|
||||
ww.codec == OPUSNX || ww.codec == OPUS || ww.codec == PTADPCM) {
|
||||
ww.truncated = 1; /* only seen those, probably all exist (XWMA, AAC, HEVAG, ATRAC9?) */
|
||||
} else {
|
||||
VGM_LOG("WWISE: wrong size, maybe truncated\n");
|
||||
goto fail;
|
||||
@ -679,6 +680,10 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE* sf) {
|
||||
vgmstream->interleave_block_size = ww.block_align / ww.channels;
|
||||
//vgmstream->codec_endian = ww.big_endian; //?
|
||||
|
||||
if (ww.truncated) {
|
||||
ww.data_size = ww.file_size - ww.data_offset;
|
||||
}
|
||||
|
||||
vgmstream->num_samples = ptadpcm_bytes_to_samples(ww.data_size, ww.channels, vgmstream->interleave_block_size);
|
||||
break;
|
||||
|
||||
|
@ -248,6 +248,7 @@ static int render_layout(sample_t* buf, int32_t sample_count, VGMSTREAM* vgmstre
|
||||
/* current_sample goes between loop points (if looped) or up to max samples,
|
||||
* must detect beyond that decoders would encounter garbage data */
|
||||
|
||||
/* not ">=" to allow layouts to loop in some cases when == happens */
|
||||
if (vgmstream->current_sample > vgmstream->num_samples) {
|
||||
int channels = vgmstream->channels;
|
||||
|
||||
|
@ -65,6 +65,8 @@ enum { VGMSTREAM_MAX_NUM_SAMPLES = 1000000000 }; /* no ~5h vgm hopefully */
|
||||
|
||||
/* The encoding type specifies the format the sound data itself takes */
|
||||
typedef enum {
|
||||
coding_SILENCE, /* generates silence */
|
||||
|
||||
/* PCM */
|
||||
coding_PCM16LE, /* little endian 16-bit PCM */
|
||||
coding_PCM16BE, /* big endian 16-bit PCM */
|
||||
@ -289,6 +291,7 @@ typedef enum {
|
||||
/* The meta type specifies how we know what we know about the file.
|
||||
* We may know because of a header we read, some of it may have been guessed from filenames, etc. */
|
||||
typedef enum {
|
||||
meta_SILENCE,
|
||||
|
||||
meta_DSP_STD, /* Nintendo standard GC ADPCM (DSP) header */
|
||||
meta_DSP_CSTR, /* Star Fox Assault "Cstr" */
|
||||
|
Loading…
x
Reference in New Issue
Block a user