Merge pull request #1447 from bnnm/awb-etc

- Add .awb with nxopus + .acb loops
- Fix some ktsr/ktsc [Fire Emblem: Three Houses (Switch)]
This commit is contained in:
bnnm 2023-11-11 13:15:08 +01:00 committed by GitHub
commit 8d428c31eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 137 additions and 28 deletions

View File

@ -433,7 +433,7 @@ Untested/outdated.
### libopus
This is used below by FFmpeg (but can be disabled), as a static lib (`.a`/`.lib`) rather than DLL.
If you wonder why use it through FFmpeg instead of directly, all work was already done for FFmpeg's opus so it was faster easier this way.
If you wonder why use it through FFmpeg instead of directly, all work was already done for FFmpeg's opus so it was faster and easier this way.
#### Source
```bat
@ -475,7 +475,7 @@ MSBuild.exe opus.sln /p:Platform=x64 /p:Configuration=Release /p:WindowsTargetPl
### FFmpeg
vgmstream's FFmpeg builds for **Windows** and static builds for **Linux** remove many unnecessary parts of FFmpeg to trim down its gigantic size, and, on Windows, are also built with the "-vgmstream" suffix to prevent clashing with other plugins. Current options can be seen in `ffmpeg_options.txt`. Shared **Linux** builds usually link to system FFmpeg without issues, while standard FFmpeg DLLs may work (renamed to -vgmstream).
FFmpeg can be compiled with *libopus* (external lib) rather than internal *opus*. This is used because FFmpeg decodes incorrectly Opus files used some in games (mostly low bitrate). In older versions this was audibly wrong, but currently the difference shouldn't be that much, but still not that accurate compared with *libopus* (PCM sample diffs of +5000), so *vgmstream* enables it. Getting *libopus* recognized can be unwieldly, so internal *opus* is a decent enough substitute (remove `--enable-libopus` and change `libopus` to `opus` in `--enable-decoder`).
FFmpeg can be compiled with *libopus* (external lib) rather than internal *opus*. This is used because FFmpeg decodes incorrectly Opus files used some in games (mostly low bitrate). In older versions this was audibly wrong, but currently the difference shouldn't be that much, but still not that accurate compared with *libopus* (PCM sample diffs of +5000), so *vgmstream* enables it. Getting *libopus* recognized can be unwieldly, so internal *opus* is a decent enough substitute (remove `--enable-libopus` and change `libopus` to `opus` in `--enable-decoder` from options, and remove `--enable-custom-modes` from *configure*).
GCC and MSVC need `yasm.exe` somewhere in `PATH` to properly compile/optimize: https://yasm.tortall.net (add `--disable-yasm` to *configure* options to disable, may decrease performance).

View File

@ -92,6 +92,7 @@ fail:
#define ACB_TABLE_BUFFER_TRACKCOMMAND 0x2000
#define ACB_TABLE_BUFFER_SYNTH 0x4000
#define ACB_TABLE_BUFFER_WAVEFORM 0x4000
#define ACB_TABLE_BUFFER_WAVEFORMEXTENSIONDATA 0x1000
#define ACB_MAX_NAMELIST 255
#define ACB_MAX_NAME 1024 /* even more is possible in rare cases [Senran Kagura Burst Re:Newal (PC)] */
@ -159,8 +160,15 @@ typedef struct {
uint16_t Id;
uint16_t PortNo;
uint8_t Streaming;
uint8_t LoopFlag;
uint16_t ExtensionData;
} Waveform_t;
typedef struct {
uint32_t LoopStart;
uint32_t LoopEnd;
} WaveformExtensionData_t;
typedef struct {
STREAMFILE* acbFile; /* original reference, don't close */
@ -178,6 +186,7 @@ typedef struct {
STREAMFILE* TrackCommandSf;
STREAMFILE* SynthSf;
STREAMFILE* WaveformSf;
STREAMFILE* WaveformExtensionDataSf;
Cue_t* Cue;
CueName_t* CueName;
@ -188,6 +197,7 @@ typedef struct {
TrackCommand_t* TrackCommand;
Synth_t* Synth;
Waveform_t* Waveform;
WaveformExtensionData_t* WaveformExtensionData;
int Cue_rows;
int CueName_rows;
@ -198,6 +208,7 @@ typedef struct {
int TrackCommand_rows;
int Synth_rows;
int Waveform_rows;
int WaveformExtensionData_rows;
/* config */
int is_memory;
@ -208,7 +219,8 @@ typedef struct {
int synth_depth;
int sequence_depth;
/* name stuff */
/* name/config stuff */
int16_t waveform_index;
int16_t cuename_index;
const char* cuename_name;
int awbname_count;
@ -297,7 +309,7 @@ static void add_acb_name(acb_header* acb, int8_t Streaming) {
static int preload_acb_waveform(acb_header* acb) {
utf_context* Table = NULL;
int* p_rows = &acb->Waveform_rows;
int i, c_Id, c_MemoryAwbId, c_StreamAwbId, c_StreamAwbPortNo, c_Streaming;
int i, c_Id, c_MemoryAwbId, c_StreamAwbId, c_StreamAwbPortNo, c_Streaming, c_LoopFlag, c_ExtensionData;
if (*p_rows)
return 1;
@ -315,6 +327,8 @@ static int preload_acb_waveform(acb_header* acb) {
c_StreamAwbId = utf_get_column(Table, "StreamAwbId");
c_StreamAwbPortNo = utf_get_column(Table, "StreamAwbPortNo");
c_Streaming = utf_get_column(Table, "Streaming");
c_LoopFlag = utf_get_column(Table, "LoopFlag");
c_ExtensionData = utf_get_column(Table, "ExtensionData");
for (i = 0; i < *p_rows; i++) {
Waveform_t* r = &acb->Waveform[i];
@ -332,6 +346,10 @@ static int preload_acb_waveform(acb_header* acb) {
r->PortNo = 0xFFFF;
}
utf_query_col_u8(Table, i, c_Streaming, &r->Streaming);
utf_query_col_u8(Table, i, c_LoopFlag, &r->LoopFlag);
r->ExtensionData = -1;
utf_query_col_u16(Table, i, c_ExtensionData, &r->ExtensionData); /* optional for newer/Switch acb */
}
utf_close(Table);
@ -346,7 +364,7 @@ static int load_acb_waveform(acb_header* acb, uint16_t Index) {
Waveform_t* r;
if (!preload_acb_waveform(acb)) goto fail;
if (Index > acb->Waveform_rows) goto fail;
if (Index >= acb->Waveform_rows) goto fail;
r = &acb->Waveform[Index];
//;VGM_LOG("acb: Waveform[%i]: Id=%i, PortNo=%i, Streaming=%i\n", Index, r->Id, r->PortNo, r->Streaming);
@ -362,6 +380,9 @@ static int load_acb_waveform(acb_header* acb, uint16_t Index) {
if ((acb->is_memory && r->Streaming == 1) || (!acb->is_memory && r->Streaming == 0))
return 1;
/* save waveid <> Index translation */
acb->waveform_index = Index;
/* aaand finally get name (phew) */
add_acb_name(acb, r->Streaming);
@ -417,7 +438,7 @@ static int load_acb_synth(acb_header* acb, uint16_t Index) {
int i, count;
if (!preload_acb_synth(acb)) goto fail;
if (Index > acb->Synth_rows) goto fail;
if (Index >= acb->Synth_rows) goto fail;
r = &acb->Synth[Index];
//;VGM_LOG("acb: Synth[%i]: Type=%x, ReferenceItems={%x,%x}\n", Index, r->Type, r->ReferenceItems_offset, r->ReferenceItems_size);
@ -615,7 +636,7 @@ static int load_acb_trackcommand(acb_header* acb, uint16_t Index) {
TrackCommand_t* r;
if (!preload_acb_trackcommand(acb)) goto fail;
if (Index > acb->TrackCommand_rows) goto fail;
if (Index >= acb->TrackCommand_rows) goto fail;
r = &acb->TrackCommand[Index];
//;VGM_LOG("acb: TrackEvent/Command[%i]: Command={%x,%x}\n", Index, r->Command_offset, r->Command_size);
@ -669,7 +690,7 @@ static int load_acb_track(acb_header* acb, uint16_t Index) {
Track_t* r;
if (!preload_acb_track(acb)) goto fail;
if (Index > acb->Track_rows) goto fail;
if (Index >= acb->Track_rows) goto fail;
r = &acb->Track[Index];
//;VGM_LOG("acb: Track[%i]: EventIndex=%i\n", Index, r->EventIndex);
@ -736,7 +757,7 @@ static int load_acb_sequence(acb_header* acb, uint16_t Index) {
int i;
if (!preload_acb_sequence(acb)) goto fail;
if (Index > acb->Sequence_rows) goto fail;
if (Index >= acb->Sequence_rows) goto fail;
r = &acb->Sequence[Index];
//;VGM_LOG("acb: Sequence[%i]: NumTracks=%i, TrackIndex={%x, %x}, Type=%x\n", Index, r->NumTracks, r->TrackIndex_offset, r->TrackIndex_size, r->Type);
@ -832,7 +853,7 @@ static int load_acb_block(acb_header* acb, uint16_t Index) {
int i;
if (!preload_acb_block(acb)) goto fail;
if (Index > acb->Block_rows) goto fail;
if (Index >= acb->Block_rows) goto fail;
r = &acb->Block[Index];
//;VGM_LOG("acb: Block[%i]: NumTracks=%i, TrackIndex={%x, %x}\n", Index, r->NumTracks, r->TrackIndex_offset, r->TrackIndex_size);
@ -900,7 +921,7 @@ static int load_acb_blocksequence(acb_header* acb, uint16_t Index) {
int i;
if (!preload_acb_blocksequence(acb)) goto fail;
if (Index > acb->BlockSequence_rows) goto fail;
if (Index >= acb->BlockSequence_rows) goto fail;
r = &acb->BlockSequence[Index];
//;VGM_LOG("acb: BlockSequence[%i]: NumTracks=%i, TrackIndex={%x, %x}, NumBlocks=%i, BlockIndex={%x, %x}\n", Index, r->NumTracks, r->TrackIndex_offset,r->TrackIndex_size, r->NumBlocks, r->BlockIndex_offset, r->BlockIndex_size);
@ -977,7 +998,7 @@ static int load_acb_cue(acb_header* acb, uint16_t Index) {
/* read Cue[Index] */
if (!preload_acb_cue(acb)) goto fail;
if (Index > acb->Cue_rows) goto fail;
if (Index >= acb->Cue_rows) goto fail;
r = &acb->Cue[Index];
//;VGM_LOG("acb: Cue[%i]: ReferenceType=%i, ReferenceIndex=%i\n", Index, r->ReferenceType, r->ReferenceIndex);
@ -1064,7 +1085,7 @@ static int load_acb_cuename(acb_header* acb, uint16_t Index) {
CueName_t* r;
if (!preload_acb_cuename(acb)) goto fail;
if (Index > acb->CueName_rows) goto fail;
if (Index >= acb->CueName_rows) goto fail;
r = &acb->CueName[Index];
//;VGM_LOG("acb: CueName[%i]: CueIndex=%i, CueName=%s\n", Index, r->CueIndex, r->CueName);
@ -1081,6 +1102,79 @@ fail:
return 0;
}
static int preload_acb_waveformextensiondata(acb_header* acb) {
utf_context* Table = NULL;
int* p_rows = &acb->WaveformExtensionData_rows;
int i, c_LoopStart, c_LoopEnd;
if (*p_rows)
return 1;
if (!open_utf_subtable(acb, &acb->WaveformExtensionDataSf, &Table, "WaveformExtensionDataTable", p_rows, ACB_TABLE_BUFFER_WAVEFORMEXTENSIONDATA))
goto fail;
if (!*p_rows)
return 1;
//;VGM_LOG("acb: preload WaveformExtensionData=%i\n", *p_rows);
acb->WaveformExtensionData = malloc(*p_rows * sizeof(WaveformExtensionData_t));
if (!acb->WaveformExtensionData) goto fail;
c_LoopStart = utf_get_column(Table, "LoopStart");
c_LoopEnd = utf_get_column(Table, "LoopEnd");
for (i = 0; i < *p_rows; i++) {
WaveformExtensionData_t* r = &acb->WaveformExtensionData[i];
utf_query_col_u32(Table, i, c_LoopStart, &r->LoopStart);
utf_query_col_u32(Table, i, c_LoopEnd, &r->LoopEnd);
}
utf_close(Table);
return 1;
fail:
VGM_LOG("acb: failed WaveformExtensionData preload\n");
utf_close(Table);
return 0;
}
/* for Switch Opus that has loop info in a separate "WaveformExtensionData" table (pointed by a field in Waveform) */
static int load_acb_loops(acb_header* acb, VGMSTREAM* vgmstream) {
Waveform_t* rw;
WaveformExtensionData_t* r;
uint16_t WaveIndex = acb->waveform_index;
uint16_t ExtensionIndex = -1;
if (vgmstream->loop_flag)
return 0;
/* assumes that will be init'd before while searching for names */
if (WaveIndex < 0) goto fail;
//if (!preload_acb_waveform(acb)) goto fail;
if (WaveIndex >= acb->Waveform_rows) goto fail;
rw = &acb->Waveform[WaveIndex];
/* 1=no loop, 2=loop, ignore others/0(default)/255 just in case */
if (rw->LoopFlag != 2)
return 0;
ExtensionIndex = rw->ExtensionData;
if (ExtensionIndex < 0) goto fail; /* not init'd? */
if (!preload_acb_waveformextensiondata(acb)) goto fail;
if (ExtensionIndex >= acb->WaveformExtensionData_rows) goto fail;
r = &acb->WaveformExtensionData[ExtensionIndex];
//;VGM_LOG("acb: WaveformExtensionData[%i]: LoopStart=%i, LoopEnd=%i\n", Index, r->LoopStart, r->LoopEnd);
vgmstream_force_loop(vgmstream, 1, r->LoopStart, r->LoopEnd);
return 1;
fail:
VGM_LOG("acb: failed WaveformExtensionData %i\n", ExtensionIndex);
return 0;
}
/*****************************************************************************/
/* Normally games load a .acb + .awb, and asks the .acb to play a cue by name or index.
@ -1111,7 +1205,7 @@ fail:
* per table, meaning it uses a decent chunk of memory, but having to re-read with streamfiles is much slower.
*/
void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int port, int is_memory) {
void load_acb_wave_info(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int port, int is_memory, int load_loops) {
acb_header acb = {0};
int i;
@ -1129,6 +1223,7 @@ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po
acb.target_waveid = waveid;
acb.target_port = port;
acb.is_memory = is_memory;
acb.waveform_index = -1;
/* read all possible cue names and find which waveids are referenced by it */
preload_acb_cuename(&acb);
@ -1143,6 +1238,11 @@ void load_acb_wave_name(STREAMFILE* sf, VGMSTREAM* vgmstream, int waveid, int po
vgmstream->stream_name[STREAM_NAME_SIZE - 1] = '\0';
}
/* uncommon */
if (load_loops) {
load_acb_loops(&acb, vgmstream);
}
/* done */
fail:
utf_close(acb.Header);
@ -1157,6 +1257,7 @@ fail:
close_streamfile(acb.TrackCommandSf);
close_streamfile(acb.SynthSf);
close_streamfile(acb.WaveformSf);
close_streamfile(acb.WaveformExtensionDataSf);
free(acb.CueName);
free(acb.Cue);
@ -1167,4 +1268,5 @@ fail:
free(acb.TrackCommand);
free(acb.Synth);
free(acb.Waveform);
free(acb.WaveformExtensionData);
}

View File

@ -4,7 +4,7 @@
//typedef enum { ADX, HCA, VAG, RIFF, CWAV, DSP, CWAC, M4A } awb_type_t;
static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid);
static void load_acb_info(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid, int load_loops);
/* AFS2/AWB (Atom Wave Bank) - CRI container of streaming audio, often together with a .acb cue sheet */
VGMSTREAM* init_vgmstream_awb(STREAMFILE* sf) {
@ -19,6 +19,7 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
uint8_t offset_size;
uint16_t waveid_alignment, offset_alignment, subkey;
int waveid;
int load_loops = 0;
/* checks */
@ -126,6 +127,11 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
extension = "m4a";
}
#endif
else if (read_u32be(subfile_offset + 0x00,sf) == 0x01000080) { /* (type 24=NXOpus) */
init_vgmstream =init_vgmstream_opus_std; /* Super Mario RPG (Switch) */
extension = "opus";
load_loops = 1; /* loops not in Opus (rare) but in .acb */
}
else { /* 12=XMA? */
vgm_logi("AWB: unknown codec (report)\n");
goto fail;
@ -144,8 +150,8 @@ VGMSTREAM* init_vgmstream_awb_memory(STREAMFILE* sf, STREAMFILE* sf_acb) {
vgmstream->num_streams = total_subsongs;
}
/* try to load cue names */
load_awb_name(sf, sf_acb, vgmstream, waveid);
/* try to load cue names+etc */
load_acb_info(sf, sf_acb, vgmstream, waveid, load_loops);
close_streamfile(temp_sf);
return vgmstream;
@ -157,7 +163,7 @@ fail:
}
static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid) {
static void load_acb_info(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstream, int waveid, int load_loops) {
int is_memory = (sf_acb != NULL);
int port = 0;
@ -170,7 +176,7 @@ static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstre
/* try parsing TXTM if present */
sf_acb = read_filemap_file_pos(sf, 0, &port);
/* try (name).awb + (name).awb */
/* try (name).awb + (name).acb */
if (!sf_acb) {
sf_acb = open_streamfile_by_ext(sf, "acb");
}
@ -204,11 +210,11 @@ static void load_awb_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstre
}
/* probably loaded */
load_acb_wave_name(sf_acb, vgmstream, waveid, port, is_memory);
load_acb_wave_info(sf_acb, vgmstream, waveid, port, is_memory, load_loops);
close_streamfile(sf_acb);
}
else {
load_acb_wave_name(sf_acb, vgmstream, waveid, port, is_memory);
load_acb_wave_info(sf_acb, vgmstream, waveid, port, is_memory, load_loops);
}
}

View File

@ -282,11 +282,11 @@ static void load_cpk_name(STREAMFILE* sf, STREAMFILE* sf_acb, VGMSTREAM* vgmstre
return;
/* companion .acb probably loaded */
load_acb_wave_name(sf_acb, vgmstream, waveid, port, is_memory);
load_acb_wave_info(sf_acb, vgmstream, waveid, port, is_memory, 0);
close_streamfile(sf_acb);
}
else {
load_acb_wave_name(sf_acb, vgmstream, waveid, port, is_memory);
load_acb_wave_info(sf_acb, vgmstream, waveid, port, is_memory, 0);
}
}

View File

@ -14,8 +14,7 @@ VGMSTREAM* init_vgmstream_ktsc(STREAMFILE* sf) {
/* checks */
if (!is_id32be(0x00, sf, "KTSC"))
goto fail;
if (read_u32be(0x04, sf) != 0x01000001) /* version? */
goto fail;
/* 0x04: version? (0x01000001: common, 0x01000004: FE Three Houses) */
/* .ktsl2asbin: common [Atelier Ryza (PC)]
* .asbin: Warriors Orochi 4 (PC) (assumed) */

View File

@ -350,11 +350,13 @@ static int parse_codec(ktsr_header* ktsr) {
if (ktsr->is_external) {
if (ktsr->format == 0x0005)
ktsr->codec = KTSS; // [Ultra Kaiju Monster Rancher (Switch)]
else if (ktsr->format == 0x1000)
ktsr->codec = KTSS; // [Fire Emblem: Three Houses (Switch)-some DSP voices]
else
goto fail;
}
else if (ktsr->format == 0x0000)
ktsr->codec = DSP; // Fire Emblem: Three Houses (Switch)
ktsr->codec = DSP; // [Fire Emblem: Three Houses (Switch)]
else
goto fail;
break;

View File

@ -819,8 +819,8 @@ VGMSTREAM * init_vgmstream_bwav(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_awb(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_awb_memory(STREAMFILE * streamFile, STREAMFILE *acbFile);
VGMSTREAM * init_vgmstream_acb(STREAMFILE * streamFile);
void load_acb_wave_name(STREAMFILE *acbFile, VGMSTREAM* vgmstream, int waveid, int port, int is_memory);
VGMSTREAM* init_vgmstream_acb(STREAMFILE* sf);
void load_acb_wave_info(STREAMFILE *acbFile, VGMSTREAM* vgmstream, int waveid, int port, int is_memory, int load_loops);
VGMSTREAM * init_vgmstream_rad(STREAMFILE * streamFile);