From 64b4798b38a6b8372026ef670dc84306fb4663af Mon Sep 17 00:00:00 2001 From: bnnm Date: Tue, 27 Aug 2024 17:30:44 +0200 Subject: [PATCH] Fix some .psb --- src/meta/psb.c | 138 +++++++++++++++++++++++-------------------------- 1 file changed, 66 insertions(+), 72 deletions(-) diff --git a/src/meta/psb.c b/src/meta/psb.c index 3c03b01c..b5908391 100644 --- a/src/meta/psb.c +++ b/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