diff --git a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj index 227030e66..f9184139f 100644 --- a/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj +++ b/Frameworks/vgmstream/libvgmstream.xcodeproj/project.pbxproj @@ -206,7 +206,6 @@ 834FE10F215C79ED000A5D3D /* sdf.c in Sources */ = {isa = PBXBuildFile; fileRef = 834FE0E6215C79EC000A5D3D /* sdf.c */; }; 834FE110215C79ED000A5D3D /* msv.c in Sources */ = {isa = PBXBuildFile; fileRef = 834FE0E7215C79EC000A5D3D /* msv.c */; }; 834FE111215C79ED000A5D3D /* ck.c in Sources */ = {isa = PBXBuildFile; fileRef = 834FE0E8215C79EC000A5D3D /* ck.c */; }; - 8350270D1ED119D200C25929 /* ps3_mta2.c in Sources */ = {isa = PBXBuildFile; fileRef = 8350270C1ED119D200C25929 /* ps3_mta2.c */; }; 835027131ED119E000C25929 /* mta2_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 835027121ED119E000C25929 /* mta2_decoder.c */; }; 8350C0551E071881009E0A93 /* xma.c in Sources */ = {isa = PBXBuildFile; fileRef = 8350C0541E071881009E0A93 /* xma.c */; }; 8350C05A1E071990009E0A93 /* ps2_svag_snk.c in Sources */ = {isa = PBXBuildFile; fileRef = 8350C0591E071990009E0A93 /* ps2_svag_snk.c */; }; @@ -241,7 +240,6 @@ 836F6F3A18BDC2190095E648 /* sdx2_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6DFC18BDC2180095E648 /* sdx2_decoder.c */; }; 836F6F3B18BDC2190095E648 /* ws_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6DFD18BDC2180095E648 /* ws_decoder.c */; }; 836F6F3C18BDC2190095E648 /* xa_decoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6DFE18BDC2180095E648 /* xa_decoder.c */; }; - 836F6F3E18BDC2190095E648 /* aix_layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E0118BDC2180095E648 /* aix_layout.c */; }; 836F6F4118BDC2190095E648 /* blocked.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E0418BDC2180095E648 /* blocked.c */; }; 836F6F4A18BDC2190095E648 /* interleave.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E0D18BDC2180095E648 /* interleave.c */; }; 836F6F4D18BDC2190095E648 /* layout.h in Headers */ = {isa = PBXBuildFile; fileRef = 836F6E1018BDC2180095E648 /* layout.h */; }; @@ -264,7 +262,6 @@ 836F6F7718BDC2190095E648 /* capdsp.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E3B18BDC2180095E648 /* capdsp.c */; }; 836F6F7818BDC2190095E648 /* Cstr.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E3C18BDC2180095E648 /* Cstr.c */; }; 836F6F7918BDC2190095E648 /* dc_asd.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E3D18BDC2180095E648 /* dc_asd.c */; }; - 836F6F7A18BDC2190095E648 /* dc_dcsw_dcs.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E3E18BDC2180095E648 /* dc_dcsw_dcs.c */; }; 836F6F7B18BDC2190095E648 /* dc_idvi.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E3F18BDC2180095E648 /* dc_idvi.c */; }; 836F6F7C18BDC2190095E648 /* dc_kcey.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E4018BDC2180095E648 /* dc_kcey.c */; }; 836F6F7D18BDC2190095E648 /* dc_str.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E4118BDC2180095E648 /* dc_str.c */; }; @@ -346,7 +343,6 @@ 836F6FDA18BDC2190095E648 /* ps2_hgc1.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E9E18BDC2180095E648 /* ps2_hgc1.c */; }; 836F6FDB18BDC2190095E648 /* ps2_hsf.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6E9F18BDC2180095E648 /* ps2_hsf.c */; }; 836F6FDC18BDC2190095E648 /* ps2_iab.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EA018BDC2180095E648 /* ps2_iab.c */; }; - 836F6FDD18BDC2190095E648 /* ps2_ikm.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EA118BDC2180095E648 /* ps2_ikm.c */; }; 836F6FDE18BDC2190095E648 /* ps2_ild.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EA218BDC2180095E648 /* ps2_ild.c */; }; 836F6FDF18BDC2190095E648 /* ps2_int.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EA318BDC2180095E648 /* ps2_int.c */; }; 836F6FE018BDC2190095E648 /* ps2_joe.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EA418BDC2180095E648 /* ps2_joe.c */; }; @@ -358,8 +354,6 @@ 836F6FE818BDC2190095E648 /* ps2_mic.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EAC18BDC2180095E648 /* ps2_mic.c */; }; 836F6FE918BDC2190095E648 /* ps2_mihb.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EAD18BDC2180095E648 /* ps2_mihb.c */; }; 836F6FEA18BDC2190095E648 /* ps2_msa.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EAE18BDC2180095E648 /* ps2_msa.c */; }; - 836F6FEC18BDC2190095E648 /* ps2_mtaf.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EB018BDC2180095E648 /* ps2_mtaf.c */; }; - 836F6FED18BDC2190095E648 /* ps2_npsf.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EB118BDC2180095E648 /* ps2_npsf.c */; }; 836F6FEE18BDC2190095E648 /* ps2_p2bt.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EB218BDC2180095E648 /* ps2_p2bt.c */; }; 836F6FEF18BDC2190095E648 /* ps2_pnb.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EB318BDC2180095E648 /* ps2_pnb.c */; }; 836F6FF218BDC2190095E648 /* ps2_rnd.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EB618BDC2180095E648 /* ps2_rnd.c */; }; @@ -388,7 +382,6 @@ 836F700F18BDC2190095E648 /* ps2_xa30.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED318BDC2190095E648 /* ps2_xa30.c */; }; 836F701118BDC2190095E648 /* ps3_cps.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED518BDC2190095E648 /* ps3_cps.c */; }; 836F701218BDC2190095E648 /* ps3_ivag.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED618BDC2190095E648 /* ps3_ivag.c */; }; - 836F701418BDC2190095E648 /* ps3_msf.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED818BDC2190095E648 /* ps3_msf.c */; }; 836F701518BDC2190095E648 /* ps3_past.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6ED918BDC2190095E648 /* ps3_past.c */; }; 836F701B18BDC2190095E648 /* psx_gms.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EDF18BDC2190095E648 /* psx_gms.c */; }; 836F701D18BDC2190095E648 /* raw.c in Sources */ = {isa = PBXBuildFile; fileRef = 836F6EE118BDC2190095E648 /* raw.c */; }; @@ -508,6 +501,30 @@ 83AB8C751E8072A100086084 /* nub_vag.c in Sources */ = {isa = PBXBuildFile; fileRef = 83AB8C731E8072A100086084 /* nub_vag.c */; }; 83AB8C761E8072A100086084 /* x360_ast.c in Sources */ = {isa = PBXBuildFile; fileRef = 83AB8C741E8072A100086084 /* x360_ast.c */; }; 83BAFB6C19F45EB3005DAB60 /* bfstm.c in Sources */ = {isa = PBXBuildFile; fileRef = 83BAFB6B19F45EB3005DAB60 /* bfstm.c */; }; + 83C7280F22BC893D00678B4A /* xwb_xsb.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C727FB22BC893800678B4A /* xwb_xsb.h */; }; + 83C7281022BC893D00678B4A /* nps.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C727FC22BC893900678B4A /* nps.c */; }; + 83C7281122BC893D00678B4A /* 9tav_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C727FD22BC893900678B4A /* 9tav_streamfile.h */; }; + 83C7281222BC893D00678B4A /* 9tav.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C727FE22BC893900678B4A /* 9tav.c */; }; + 83C7281322BC893D00678B4A /* mta2.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C727FF22BC893900678B4A /* mta2.c */; }; + 83C7281422BC893D00678B4A /* ffdl.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7280022BC893A00678B4A /* ffdl.c */; }; + 83C7281522BC893D00678B4A /* txth_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C7280122BC893A00678B4A /* txth_streamfile.h */; }; + 83C7281622BC893D00678B4A /* xwma_konami_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C7280222BC893A00678B4A /* xwma_konami_streamfile.h */; }; + 83C7281722BC893D00678B4A /* mtaf.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7280322BC893A00678B4A /* mtaf.c */; }; + 83C7281822BC893D00678B4A /* sfh_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C7280422BC893B00678B4A /* sfh_streamfile.h */; }; + 83C7281922BC893D00678B4A /* fsb5_fev.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7280522BC893B00678B4A /* fsb5_fev.c */; }; + 83C7281A22BC893D00678B4A /* mus_vc.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7280622BC893B00678B4A /* mus_vc.c */; }; + 83C7281B22BC893D00678B4A /* strm_abylight.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7280722BC893B00678B4A /* strm_abylight.c */; }; + 83C7281C22BC893D00678B4A /* sfh.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7280822BC893C00678B4A /* sfh.c */; }; + 83C7281D22BC893D00678B4A /* ikm.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7280922BC893C00678B4A /* ikm.c */; }; + 83C7281E22BC893D00678B4A /* msf.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7280A22BC893C00678B4A /* msf.c */; }; + 83C7281F22BC893D00678B4A /* xwma_konami.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7280B22BC893C00678B4A /* xwma_konami.c */; }; + 83C7282022BC893D00678B4A /* dcs_wav.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7280C22BC893D00678B4A /* dcs_wav.c */; }; + 83C7282122BC893D00678B4A /* msf_konami.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7280D22BC893D00678B4A /* msf_konami.c */; }; + 83C7282222BC893D00678B4A /* mta2_streamfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C7280E22BC893D00678B4A /* mta2_streamfile.h */; }; + 83C7282722BC8C1500678B4A /* plugins.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C7282322BC8C1300678B4A /* plugins.h */; }; + 83C7282822BC8C1500678B4A /* mixing.h in Headers */ = {isa = PBXBuildFile; fileRef = 83C7282422BC8C1400678B4A /* mixing.h */; }; + 83C7282922BC8C1500678B4A /* mixing.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7282522BC8C1400678B4A /* mixing.c */; }; + 83C7282A22BC8C1500678B4A /* plugins.c in Sources */ = {isa = PBXBuildFile; fileRef = 83C7282622BC8C1400678B4A /* plugins.c */; }; 83CAB8E21F0B0752001BC993 /* wii_04sw.c in Sources */ = {isa = PBXBuildFile; fileRef = 83CAB8E11F0B0745001BC993 /* wii_04sw.c */; }; 83CAB8E31F0B0755001BC993 /* pc_xa30.c in Sources */ = {isa = PBXBuildFile; fileRef = 83CAB8DC1F0B0744001BC993 /* pc_xa30.c */; }; 83CD428A1F787879000F77BE /* libswresample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 83CD42851F787878000F77BE /* libswresample.a */; }; @@ -849,7 +866,6 @@ 834FE0E6215C79EC000A5D3D /* sdf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sdf.c; sourceTree = ""; }; 834FE0E7215C79EC000A5D3D /* msv.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = msv.c; sourceTree = ""; }; 834FE0E8215C79EC000A5D3D /* ck.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ck.c; sourceTree = ""; }; - 8350270C1ED119D200C25929 /* ps3_mta2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps3_mta2.c; sourceTree = ""; }; 835027121ED119E000C25929 /* mta2_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mta2_decoder.c; sourceTree = ""; }; 8350C0541E071881009E0A93 /* xma.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xma.c; sourceTree = ""; }; 8350C0591E071990009E0A93 /* ps2_svag_snk.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_svag_snk.c; sourceTree = ""; }; @@ -886,7 +902,6 @@ 836F6DFC18BDC2180095E648 /* sdx2_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sdx2_decoder.c; sourceTree = ""; }; 836F6DFD18BDC2180095E648 /* ws_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ws_decoder.c; sourceTree = ""; }; 836F6DFE18BDC2180095E648 /* xa_decoder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xa_decoder.c; sourceTree = ""; }; - 836F6E0118BDC2180095E648 /* aix_layout.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = aix_layout.c; sourceTree = ""; }; 836F6E0418BDC2180095E648 /* blocked.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = blocked.c; sourceTree = ""; }; 836F6E0D18BDC2180095E648 /* interleave.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = interleave.c; sourceTree = ""; }; 836F6E1018BDC2180095E648 /* layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = layout.h; sourceTree = ""; }; @@ -909,7 +924,6 @@ 836F6E3B18BDC2180095E648 /* capdsp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = capdsp.c; sourceTree = ""; }; 836F6E3C18BDC2180095E648 /* Cstr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Cstr.c; sourceTree = ""; }; 836F6E3D18BDC2180095E648 /* dc_asd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dc_asd.c; sourceTree = ""; }; - 836F6E3E18BDC2180095E648 /* dc_dcsw_dcs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dc_dcsw_dcs.c; sourceTree = ""; }; 836F6E3F18BDC2180095E648 /* dc_idvi.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dc_idvi.c; sourceTree = ""; }; 836F6E4018BDC2180095E648 /* dc_kcey.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dc_kcey.c; sourceTree = ""; }; 836F6E4118BDC2180095E648 /* dc_str.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dc_str.c; sourceTree = ""; }; @@ -991,7 +1005,6 @@ 836F6E9E18BDC2180095E648 /* ps2_hgc1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_hgc1.c; sourceTree = ""; }; 836F6E9F18BDC2180095E648 /* ps2_hsf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_hsf.c; sourceTree = ""; }; 836F6EA018BDC2180095E648 /* ps2_iab.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_iab.c; sourceTree = ""; }; - 836F6EA118BDC2180095E648 /* ps2_ikm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_ikm.c; sourceTree = ""; }; 836F6EA218BDC2180095E648 /* ps2_ild.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_ild.c; sourceTree = ""; }; 836F6EA318BDC2180095E648 /* ps2_int.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_int.c; sourceTree = ""; }; 836F6EA418BDC2180095E648 /* ps2_joe.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_joe.c; sourceTree = ""; }; @@ -1003,8 +1016,6 @@ 836F6EAC18BDC2180095E648 /* ps2_mic.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_mic.c; sourceTree = ""; }; 836F6EAD18BDC2180095E648 /* ps2_mihb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_mihb.c; sourceTree = ""; }; 836F6EAE18BDC2180095E648 /* ps2_msa.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_msa.c; sourceTree = ""; }; - 836F6EB018BDC2180095E648 /* ps2_mtaf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_mtaf.c; sourceTree = ""; }; - 836F6EB118BDC2180095E648 /* ps2_npsf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_npsf.c; sourceTree = ""; }; 836F6EB218BDC2180095E648 /* ps2_p2bt.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_p2bt.c; sourceTree = ""; }; 836F6EB318BDC2180095E648 /* ps2_pnb.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_pnb.c; sourceTree = ""; }; 836F6EB618BDC2180095E648 /* ps2_rnd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_rnd.c; sourceTree = ""; }; @@ -1033,7 +1044,6 @@ 836F6ED318BDC2190095E648 /* ps2_xa30.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps2_xa30.c; sourceTree = ""; }; 836F6ED518BDC2190095E648 /* ps3_cps.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps3_cps.c; sourceTree = ""; }; 836F6ED618BDC2190095E648 /* ps3_ivag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps3_ivag.c; sourceTree = ""; }; - 836F6ED818BDC2190095E648 /* ps3_msf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps3_msf.c; sourceTree = ""; }; 836F6ED918BDC2190095E648 /* ps3_past.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ps3_past.c; sourceTree = ""; }; 836F6EDF18BDC2190095E648 /* psx_gms.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = psx_gms.c; sourceTree = ""; }; 836F6EE118BDC2190095E648 /* raw.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = raw.c; sourceTree = ""; }; @@ -1152,6 +1162,30 @@ 83AB8C731E8072A100086084 /* nub_vag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nub_vag.c; sourceTree = ""; }; 83AB8C741E8072A100086084 /* x360_ast.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x360_ast.c; sourceTree = ""; }; 83BAFB6B19F45EB3005DAB60 /* bfstm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bfstm.c; sourceTree = ""; }; + 83C727FB22BC893800678B4A /* xwb_xsb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xwb_xsb.h; sourceTree = ""; }; + 83C727FC22BC893900678B4A /* nps.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nps.c; sourceTree = ""; }; + 83C727FD22BC893900678B4A /* 9tav_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 9tav_streamfile.h; sourceTree = ""; }; + 83C727FE22BC893900678B4A /* 9tav.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = 9tav.c; sourceTree = ""; }; + 83C727FF22BC893900678B4A /* mta2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mta2.c; sourceTree = ""; }; + 83C7280022BC893A00678B4A /* ffdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ffdl.c; sourceTree = ""; }; + 83C7280122BC893A00678B4A /* txth_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = txth_streamfile.h; sourceTree = ""; }; + 83C7280222BC893A00678B4A /* xwma_konami_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xwma_konami_streamfile.h; sourceTree = ""; }; + 83C7280322BC893A00678B4A /* mtaf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mtaf.c; sourceTree = ""; }; + 83C7280422BC893B00678B4A /* sfh_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sfh_streamfile.h; sourceTree = ""; }; + 83C7280522BC893B00678B4A /* fsb5_fev.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fsb5_fev.c; sourceTree = ""; }; + 83C7280622BC893B00678B4A /* mus_vc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mus_vc.c; sourceTree = ""; }; + 83C7280722BC893B00678B4A /* strm_abylight.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = strm_abylight.c; sourceTree = ""; }; + 83C7280822BC893C00678B4A /* sfh.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sfh.c; sourceTree = ""; }; + 83C7280922BC893C00678B4A /* ikm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ikm.c; sourceTree = ""; }; + 83C7280A22BC893C00678B4A /* msf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = msf.c; sourceTree = ""; }; + 83C7280B22BC893C00678B4A /* xwma_konami.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = xwma_konami.c; sourceTree = ""; }; + 83C7280C22BC893D00678B4A /* dcs_wav.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dcs_wav.c; sourceTree = ""; }; + 83C7280D22BC893D00678B4A /* msf_konami.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = msf_konami.c; sourceTree = ""; }; + 83C7280E22BC893D00678B4A /* mta2_streamfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mta2_streamfile.h; sourceTree = ""; }; + 83C7282322BC8C1300678B4A /* plugins.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = plugins.h; sourceTree = ""; }; + 83C7282422BC8C1400678B4A /* mixing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mixing.h; sourceTree = ""; }; + 83C7282522BC8C1400678B4A /* mixing.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mixing.c; sourceTree = ""; }; + 83C7282622BC8C1400678B4A /* plugins.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = plugins.c; sourceTree = ""; }; 83CAB8DC1F0B0744001BC993 /* pc_xa30.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = pc_xa30.c; sourceTree = ""; }; 83CAB8E11F0B0745001BC993 /* wii_04sw.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = wii_04sw.c; sourceTree = ""; }; 83CD42851F787878000F77BE /* libswresample.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libswresample.a; path = ../../ThirdParty/ffmpeg/lib/libswresample.a; sourceTree = ""; }; @@ -1305,6 +1339,10 @@ 836F6DFF18BDC2180095E648 /* layout */, 836F6E2718BDC2180095E648 /* meta */, 83A3F0711E3AD8B900D6A794 /* formats.c */, + 83C7282522BC8C1400678B4A /* mixing.c */, + 83C7282422BC8C1400678B4A /* mixing.h */, + 83C7282622BC8C1400678B4A /* plugins.c */, + 83C7282322BC8C1300678B4A /* plugins.h */, 83A3F0731E3AD8B900D6A794 /* stack_alloc.h */, 836F6F1718BDC2190095E648 /* streamfile.c */, 836F6F1818BDC2190095E648 /* streamfile.h */, @@ -1394,7 +1432,6 @@ 836F6DFF18BDC2180095E648 /* layout */ = { isa = PBXGroup; children = ( - 836F6E0118BDC2180095E648 /* aix_layout.c */, 8306B08E2098454E000302D4 /* blocked_adm.c */, 8306B0882098454C000302D4 /* blocked_ast.c */, 83AA5D1B1F6E2F7F0020821C /* blocked_awc.c */, @@ -1447,6 +1484,8 @@ isa = PBXGroup; children = ( 836F6E2918BDC2180095E648 /* 2dx9.c */, + 83C727FD22BC893900678B4A /* 9tav_streamfile.h */, + 83C727FE22BC893900678B4A /* 9tav.c */, 8351F32A2212B57000A606E4 /* 208.c */, 834FE0C8215C79E7000A5D3D /* a2m.c */, 8306B0C82098458D000302D4 /* aax_utf.h */, @@ -1493,10 +1532,10 @@ 834FE0D8215C79EA000A5D3D /* csmp.c */, 836F6E3C18BDC2180095E648 /* Cstr.c */, 836F6E3D18BDC2180095E648 /* dc_asd.c */, - 836F6E3E18BDC2180095E648 /* dc_dcsw_dcs.c */, 836F6E3F18BDC2180095E648 /* dc_idvi.c */, 836F6E4018BDC2180095E648 /* dc_kcey.c */, 836F6E4118BDC2180095E648 /* dc_str.c */, + 83C7280C22BC893D00678B4A /* dcs_wav.c */, 8349A8EE1FE6257C00E26435 /* dec.c */, 834FE0CD215C79E8000A5D3D /* derf.c */, 836F6E4318BDC2180095E648 /* dmsg_segh.c */, @@ -1516,6 +1555,7 @@ 836F6E4A18BDC2180095E648 /* excitebots.c */, 8349A8EF1FE6257C00E26435 /* ezw.c */, 832BF80821E05135006F50F1 /* fag.c */, + 83C7280022BC893A00678B4A /* ffdl.c */, 838BDB6D1D3B043C0022CA6F /* ffmpeg.c */, 836F6E4B18BDC2180095E648 /* ffw.c */, 8349A8FD1FE6257F00E26435 /* flx.c */, @@ -1523,6 +1563,7 @@ 834FE0C4215C79E6000A5D3D /* fsb_interleave_streamfile.h */, 83A21F7E201D8980000F04B9 /* fsb_keys.h */, 836F6E4C18BDC2180095E648 /* fsb.c */, + 83C7280522BC893B00678B4A /* fsb5_fev.c */, 834FE0E3215C79EC000A5D3D /* fsb5_interleave_streamfile.h */, 83F5F8821908D0A400C8E65F /* fsb5.c */, 834D3A6D19F47C98001C54F6 /* g1l.c */, @@ -1540,6 +1581,7 @@ 834FE0DF215C79EB000A5D3D /* hd3_bd3.c */, 836F6E5318BDC2180095E648 /* his.c */, 834FE0E0215C79EB000A5D3D /* idsp_ie.c */, + 83C7280922BC893C00678B4A /* ikm.c */, 832BF81121E05149006F50F1 /* imc.c */, 836F6E5518BDC2180095E648 /* ios_psnd.c */, 836F6E5618BDC2180095E648 /* ish_isd.c */, @@ -1561,12 +1603,18 @@ 836F6E6018BDC2180095E648 /* mp4.c */, 8306B0CB2098458E000302D4 /* msb_msh.c */, 832BF81721E0514A006F50F1 /* msf_banpresto.c */, + 83C7280D22BC893D00678B4A /* msf_konami.c */, 832BF80B21E05148006F50F1 /* msf_tamasoft.c */, + 83C7280A22BC893C00678B4A /* msf.c */, 83709E011ECBC1A4005C03D3 /* mss.c */, 834FE0E7215C79EC000A5D3D /* msv.c */, 836F6E6118BDC2180095E648 /* msvp.c */, + 83C7280E22BC893D00678B4A /* mta2_streamfile.h */, + 83C727FF22BC893900678B4A /* mta2.c */, + 83C7280322BC893A00678B4A /* mtaf.c */, 832BF81221E05149006F50F1 /* mul.c */, 836F6E6218BDC2180095E648 /* mus_acm.c */, + 83C7280622BC893B00678B4A /* mus_vc.c */, 836F6E6318BDC2180095E648 /* musc.c */, 836F6E6418BDC2180095E648 /* musx.c */, 836F6E6518BDC2180095E648 /* myspd.c */, @@ -1598,6 +1646,7 @@ 8349A8FA1FE6257E00E26435 /* ngc_vid1.c */, 836F6E7E18BDC2180095E648 /* ngc_ymf.c */, 836F6E7F18BDC2180095E648 /* ngca.c */, + 83C727FC22BC893900678B4A /* nps.c */, 834FE0E1215C79EB000A5D3D /* nub_idsp.c */, 83AB8C731E8072A100086084 /* nub_vag.c */, 83345A4B1F8AEB2700B2EAA4 /* nub_xma.c */, @@ -1647,7 +1696,6 @@ 836F6E9E18BDC2180095E648 /* ps2_hgc1.c */, 836F6E9F18BDC2180095E648 /* ps2_hsf.c */, 836F6EA018BDC2180095E648 /* ps2_iab.c */, - 836F6EA118BDC2180095E648 /* ps2_ikm.c */, 836F6EA218BDC2180095E648 /* ps2_ild.c */, 836F6EA318BDC2180095E648 /* ps2_int.c */, 836F6EA418BDC2180095E648 /* ps2_joe.c */, @@ -1659,8 +1707,6 @@ 836F6EAC18BDC2180095E648 /* ps2_mic.c */, 836F6EAD18BDC2180095E648 /* ps2_mihb.c */, 836F6EAE18BDC2180095E648 /* ps2_msa.c */, - 836F6EB018BDC2180095E648 /* ps2_mtaf.c */, - 836F6EB118BDC2180095E648 /* ps2_npsf.c */, 836F6EB218BDC2180095E648 /* ps2_p2bt.c */, 8349A8F21FE6257D00E26435 /* ps2_pcm.c */, 836F6EB318BDC2180095E648 /* ps2_pnb.c */, @@ -1694,8 +1740,6 @@ 836F6ED318BDC2190095E648 /* ps2_xa30.c */, 836F6ED518BDC2190095E648 /* ps3_cps.c */, 836F6ED618BDC2190095E648 /* ps3_ivag.c */, - 836F6ED818BDC2190095E648 /* ps3_msf.c */, - 8350270C1ED119D200C25929 /* ps3_mta2.c */, 836F6ED918BDC2190095E648 /* ps3_past.c */, 836F6EDF18BDC2190095E648 /* psx_gms.c */, 836F6EE118BDC2190095E648 /* raw.c */, @@ -1719,6 +1763,8 @@ 834FE0E6215C79EC000A5D3D /* sdf.c */, 836F6EEF18BDC2190095E648 /* sdt.c */, 836F6EF018BDC2190095E648 /* seg.c */, + 83C7280422BC893B00678B4A /* sfh_streamfile.h */, + 83C7280822BC893C00678B4A /* sfh.c */, 836F6EF118BDC2190095E648 /* sfl.c */, 831BA6111EAC61A500CF89B0 /* sgxd.c */, 839E21EA1F2EDB0500EE54D7 /* sk_aud.c */, @@ -1737,6 +1783,7 @@ 836F6EF618BDC2190095E648 /* str_asr.c */, 836F6EF718BDC2190095E648 /* str_snds.c */, 834FE0C2215C79E6000A5D3D /* str_wav.c */, + 83C7280722BC893B00678B4A /* strm_abylight.c */, 836F6EF818BDC2190095E648 /* stx.c */, 834FE0D7215C79EA000A5D3D /* svg.c */, 836F6EF918BDC2190095E648 /* svs.c */, @@ -1744,6 +1791,7 @@ 83709E031ECBC1A4005C03D3 /* ta_aac.c */, 836F6EFA18BDC2190095E648 /* thp.c */, 836F6EFB18BDC2190095E648 /* tun.c */, + 83C7280122BC893A00678B4A /* txth_streamfile.h */, 830165971F256BD000CA0941 /* txth.c */, 8306B0D22098458F000302D4 /* txtp.c */, 8351F32B2212B57000A606E4 /* ubi_bao_streamfile.h */, @@ -1809,8 +1857,11 @@ 836F6F1218BDC2190095E648 /* xss.c */, 834FE0C6215C79E7000A5D3D /* xvag_streamfile.h */, 83345A4E1F8AEB2800B2EAA4 /* xvag.c */, + 83C727FB22BC893800678B4A /* xwb_xsb.h */, 836F6F1318BDC2190095E648 /* xwb.c */, 83A21F7D201D8980000F04B9 /* xwc.c */, + 83C7280222BC893A00678B4A /* xwma_konami_streamfile.h */, + 83C7280B22BC893C00678B4A /* xwma_konami.c */, 832BF81621E0514A006F50F1 /* xwma.c */, 836F6F1418BDC2190095E648 /* ydsp.c */, 836F6F1518BDC2190095E648 /* zsd.c */, @@ -1857,6 +1908,7 @@ files = ( 836F705518BDC2190095E648 /* streamtypes.h in Headers */, 836F6F3518BDC2190095E648 /* nwa_decoder.h in Headers */, + 83C7282222BC893D00678B4A /* mta2_streamfile.h in Headers */, 83A21F87201D8981000F04B9 /* fsb_keys.h in Headers */, 834FE0EC215C79ED000A5D3D /* kma9_streamfile.h in Headers */, 8349A90F1FE6258200E26435 /* aix_streamfile.h in Headers */, @@ -1864,6 +1916,7 @@ 8351F32E2212B57000A606E4 /* ubi_bao_streamfile.h in Headers */, 8349A9111FE6258200E26435 /* bar_streamfile.h in Headers */, 836F6F2718BDC2190095E648 /* g72x_state.h in Headers */, + 83C7282822BC8C1500678B4A /* mixing.h in Headers */, 832BF82C21E0514B006F50F1 /* hca_keys_awb.h in Headers */, 8306B0E020984590000302D4 /* ppst_streamfile.h in Headers */, 834FE0BA215C798C000A5D3D /* ea_mt_decoder_utk.h in Headers */, @@ -1873,22 +1926,28 @@ 836F705418BDC2190095E648 /* streamfile.h in Headers */, 83A3F0761E3AD8B900D6A794 /* stack_alloc.h in Headers */, 8323894B1D22419B00482226 /* clHCA.h in Headers */, + 83C7281122BC893D00678B4A /* 9tav_streamfile.h in Headers */, + 83C7280F22BC893D00678B4A /* xwb_xsb.h in Headers */, + 83C7281622BC893D00678B4A /* xwma_konami_streamfile.h in Headers */, 839E21E81F2EDAF100EE54D7 /* mpeg_decoder.h in Headers */, 834FE10C215C79ED000A5D3D /* fsb5_interleave_streamfile.h in Headers */, 839E21E51F2EDAF100EE54D7 /* vorbis_custom_decoder.h in Headers */, 8306B0E320984590000302D4 /* aax_utf.h in Headers */, 8306B0E120984590000302D4 /* ubi_lyn_ogg_streamfile.h in Headers */, 836F705918BDC2190095E648 /* vgmstream.h in Headers */, + 83C7281522BC893D00678B4A /* txth_streamfile.h in Headers */, 8306B0DE20984590000302D4 /* awc_xma_streamfile.h in Headers */, 834FE0B5215C798C000A5D3D /* acm_decoder_libacm.h in Headers */, 839E21E61F2EDAF100EE54D7 /* vorbis_custom_data_wwise.h in Headers */, 834FE103215C79ED000A5D3D /* ea_schl_streamfile.h in Headers */, + 83C7282722BC8C1500678B4A /* plugins.h in Headers */, 83F0AA6021E2028C004BBC04 /* vsv_streamfile.h in Headers */, 48C2650F1A5D420800A0A3D6 /* vorbisfile.h in Headers */, 836F705718BDC2190095E648 /* util.h in Headers */, 836F6F9A18BDC2190095E648 /* meta.h in Headers */, 8306B0D820984590000302D4 /* ea_eaac_streamfile.h in Headers */, 8306B0E820984590000302D4 /* opus_interleave_streamfile.h in Headers */, + 83C7281822BC893D00678B4A /* sfh_streamfile.h in Headers */, 834FE0EF215C79ED000A5D3D /* xvag_streamfile.h in Headers */, 8349A90C1FE6258200E26435 /* sqex_scd_streamfile.h in Headers */, 8349A91B1FE6258200E26435 /* adx_keys.h in Headers */, @@ -1945,6 +2004,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, es, ); @@ -2060,12 +2120,14 @@ 834FE104215C79ED000A5D3D /* nxa.c in Sources */, 8349A9071FE6258200E26435 /* dec.c in Sources */, 839E21E91F2EDAF100EE54D7 /* vorbis_custom_utils_sk.c in Sources */, + 83C7281422BC893D00678B4A /* ffdl.c in Sources */, 832BF80721E050DC006F50F1 /* blocked_vs_str.c in Sources */, 839E21E41F2EDAF100EE54D7 /* vorbis_custom_utils_fsb.c in Sources */, 839E21E11F2EDAF100EE54D7 /* vorbis_custom_decoder.c in Sources */, 839E21E71F2EDAF100EE54D7 /* mpeg_custom_utils.c in Sources */, 839E21E31F2EDAF100EE54D7 /* mpeg_custom_utils_ahx.c in Sources */, 8306B0AF20984552000302D4 /* blocked_rws.c in Sources */, + 83C7281A22BC893D00678B4A /* mus_vc.c in Sources */, 839E21EB1F2EDB0600EE54D7 /* sk_aud.c in Sources */, 834FE0F1215C79ED000A5D3D /* a2m.c in Sources */, 8301659A1F256BD000CA0941 /* txth.c in Sources */, @@ -2120,6 +2182,7 @@ 834FE109215C79ED000A5D3D /* idsp_ie.c in Sources */, 83299FD01E7660C7003A3242 /* bik.c in Sources */, 836F6F3318BDC2190095E648 /* ngc_dtk_decoder.c in Sources */, + 83C7281322BC893D00678B4A /* mta2.c in Sources */, 8306B0EF20984590000302D4 /* ubi_bao.c in Sources */, 836F6FBB18BDC2190095E648 /* ngca.c in Sources */, 8306B0E220984590000302D4 /* smv.c in Sources */, @@ -2144,6 +2207,7 @@ 834FE105215C79ED000A5D3D /* xmd.c in Sources */, 834FE0F6215C79ED000A5D3D /* derf.c in Sources */, 836F6F8B18BDC2190095E648 /* genh.c in Sources */, + 83C7281922BC893D00678B4A /* fsb5_fev.c in Sources */, 8349A8EA1FE6253900E26435 /* blocked_ea_schl.c in Sources */, 836F705118BDC2190095E648 /* zsd.c in Sources */, 8349A91F1FE6258200E26435 /* naac.c in Sources */, @@ -2168,7 +2232,6 @@ 836F6F7818BDC2190095E648 /* Cstr.c in Sources */, 836F6F1E18BDC2190095E648 /* acm_decoder.c in Sources */, 836F6FD818BDC2190095E648 /* ps2_gbts.c in Sources */, - 836F6F7A18BDC2190095E648 /* dc_dcsw_dcs.c in Sources */, 831BA61A1EAC61A500CF89B0 /* ps2_vds_vdm.c in Sources */, 836F6FF218BDC2190095E648 /* ps2_rnd.c in Sources */, 83709E0D1ECBC1C3005C03D3 /* mc3_decoder.c in Sources */, @@ -2185,13 +2248,11 @@ 836F6F6C18BDC2190095E648 /* ahx.c in Sources */, 83AB8C751E8072A100086084 /* nub_vag.c in Sources */, 836F702D18BDC2190095E648 /* sfl.c in Sources */, - 836F6FEC18BDC2190095E648 /* ps2_mtaf.c in Sources */, 83D7318C1A749EEE00CA1366 /* g719_decoder.c in Sources */, 836F701118BDC2190095E648 /* ps3_cps.c in Sources */, 8306B0DA20984590000302D4 /* ea_wve_au00.c in Sources */, 83A21F85201D8981000F04B9 /* atx.c in Sources */, 8306B0E720984590000302D4 /* opus_ppp.c in Sources */, - 836F701418BDC2190095E648 /* ps3_msf.c in Sources */, 836F6F6518BDC2190095E648 /* 2dx9.c in Sources */, 830EBE102004655D0023AA10 /* atrac9_decoder.c in Sources */, 836F700818BDC2190095E648 /* ps2_vms.c in Sources */, @@ -2227,6 +2288,7 @@ 836F6FCB18BDC2190095E648 /* ps2_ads.c in Sources */, 834FE108215C79ED000A5D3D /* hd3_bd3.c in Sources */, 836F6FD318BDC2190095E648 /* ps2_ccc.c in Sources */, + 83C7281C22BC893D00678B4A /* sfh.c in Sources */, 834FE0FC215C79ED000A5D3D /* vai.c in Sources */, 83AA5D171F6E2F600020821C /* mpeg_custom_utils_ealayer3.c in Sources */, 83345A521F8AEB2800B2EAA4 /* xvag.c in Sources */, @@ -2234,6 +2296,7 @@ 836F6F3918BDC2190095E648 /* SASSC_decoder.c in Sources */, 8306B0A920984552000302D4 /* blocked_adm.c in Sources */, 836F703A18BDC2190095E648 /* vs.c in Sources */, + 83C7282022BC893D00678B4A /* dcs_wav.c in Sources */, 8306B0F220984590000302D4 /* ubi_jade.c in Sources */, 836F6FF918BDC2190095E648 /* ps2_snd.c in Sources */, 836F6F2918BDC2190095E648 /* l5_555_decoder.c in Sources */, @@ -2250,6 +2313,7 @@ 8350C0551E071881009E0A93 /* xma.c in Sources */, 836F6F3218BDC2190095E648 /* ngc_dsp_decoder.c in Sources */, 836F704218BDC2190095E648 /* wii_sts.c in Sources */, + 83C7281722BC893D00678B4A /* mtaf.c in Sources */, 836F703918BDC2190095E648 /* vgs.c in Sources */, 834FE0F8215C79ED000A5D3D /* adpcm_capcom.c in Sources */, 836F6F2C18BDC2190095E648 /* mp4_aac_decoder.c in Sources */, @@ -2264,8 +2328,8 @@ 836F6FDA18BDC2190095E648 /* ps2_hgc1.c in Sources */, 836F702C18BDC2190095E648 /* seg.c in Sources */, 836F700918BDC2190095E648 /* ps2_voi.c in Sources */, - 836F6F3E18BDC2190095E648 /* aix_layout.c in Sources */, 836F6FE018BDC2190095E648 /* ps2_joe.c in Sources */, + 83C7282A22BC8C1500678B4A /* plugins.c in Sources */, 832BF82721E0514B006F50F1 /* xa.c in Sources */, 8306B0A220984552000302D4 /* blocked_bdsp.c in Sources */, 836F700118BDC2190095E648 /* ps2_tec.c in Sources */, @@ -2290,6 +2354,7 @@ 836F6F6D18BDC2190095E648 /* aifc.c in Sources */, 836F702218BDC2190095E648 /* rsd.c in Sources */, 8349A90D1FE6258200E26435 /* ubi_sb.c in Sources */, + 83C7281D22BC893D00678B4A /* ikm.c in Sources */, 83345A501F8AEB2800B2EAA4 /* pc_al2.c in Sources */, 834FE0BB215C798C000A5D3D /* celt_fsb_decoder.c in Sources */, 836F702518BDC2190095E648 /* rwx.c in Sources */, @@ -2299,6 +2364,8 @@ 834FE0BF215C79A9000A5D3D /* flat.c in Sources */, 836F6F6B18BDC2190095E648 /* agsc.c in Sources */, 836F700E18BDC2190095E648 /* ps2_xa2.c in Sources */, + 83C7281022BC893D00678B4A /* nps.c in Sources */, + 83C7281E22BC893D00678B4A /* msf.c in Sources */, 836F6FF718BDC2190095E648 /* ps2_sl3.c in Sources */, 8351F32D2212B57000A606E4 /* 208.c in Sources */, 836F6F3118BDC2190095E648 /* ngc_afc_decoder.c in Sources */, @@ -2328,7 +2395,6 @@ 836F701518BDC2190095E648 /* ps3_past.c in Sources */, 832BF80621E050DC006F50F1 /* blocked_vs_square.c in Sources */, 836F6F7C18BDC2190095E648 /* dc_kcey.c in Sources */, - 836F6FED18BDC2190095E648 /* ps2_npsf.c in Sources */, 83A21F8B201D8982000F04B9 /* sps_n1.c in Sources */, 836F6F9E18BDC2190095E648 /* mus_acm.c in Sources */, 83709E0E1ECBC1C3005C03D3 /* psv_decoder.c in Sources */, @@ -2338,6 +2404,7 @@ 836F6FCD18BDC2190095E648 /* ps2_ass.c in Sources */, 8349A90B1FE6258200E26435 /* ps2_pcm.c in Sources */, 836F6F4A18BDC2190095E648 /* interleave.c in Sources */, + 83C7281F22BC893D00678B4A /* xwma_konami.c in Sources */, 83EDE5D81A70951A005F5D84 /* mca.c in Sources */, 834FE0F3215C79ED000A5D3D /* bnk_sony.c in Sources */, 8306B0AE20984552000302D4 /* blocked_filp.c in Sources */, @@ -2349,6 +2416,7 @@ 836F704618BDC2190095E648 /* x360_tra.c in Sources */, 834FE0F0215C79ED000A5D3D /* apc.c in Sources */, 836F6FFA18BDC2190095E648 /* ps2_spm.c in Sources */, + 83C7281B22BC893D00678B4A /* strm_abylight.c in Sources */, 834D3A6E19F47C98001C54F6 /* g1l.c in Sources */, 836F6FCE18BDC2190095E648 /* ps2_ast.c in Sources */, 832BF82A21E0514B006F50F1 /* vs_square.c in Sources */, @@ -2430,6 +2498,7 @@ 836F6FB118BDC2190095E648 /* ngc_ffcc_str.c in Sources */, 8306B0B620984552000302D4 /* blocked_hwas.c in Sources */, 836F6FC218BDC2190095E648 /* pc_mxst.c in Sources */, + 83C7282922BC8C1500678B4A /* mixing.c in Sources */, 8375737621F950ED00F01AF5 /* gin.c in Sources */, 836F6FC918BDC2190095E648 /* ps2_2pfs.c in Sources */, 836F704818BDC2190095E648 /* xbox_ims.c in Sources */, @@ -2460,11 +2529,12 @@ 836F704F18BDC2190095E648 /* xwb.c in Sources */, 8306B0AD20984552000302D4 /* blocked_caf.c in Sources */, 8306B0AA20984552000302D4 /* blocked_sthd.c in Sources */, - 8350270D1ED119D200C25929 /* ps3_mta2.c in Sources */, 836F6FE918BDC2190095E648 /* ps2_mihb.c in Sources */, 836F6FA918BDC2190095E648 /* ngc_adpdtk.c in Sources */, 836F6FDC18BDC2190095E648 /* ps2_iab.c in Sources */, + 83C7282122BC893D00678B4A /* msf_konami.c in Sources */, 8315958920FEC83F007002F0 /* asf.c in Sources */, + 83C7281222BC893D00678B4A /* 9tav.c in Sources */, 836F6F3818BDC2190095E648 /* psx_decoder.c in Sources */, 836F6F2D18BDC2190095E648 /* mpeg_decoder.c in Sources */, 836F6F2618BDC2190095E648 /* g7221_decoder.c in Sources */, @@ -2472,7 +2542,6 @@ 836F6FA218BDC2190095E648 /* naomi_adpcm.c in Sources */, 836F6FBF18BDC2190095E648 /* otm.c in Sources */, 8306B0B420984552000302D4 /* blocked_tra.c in Sources */, - 836F6FDD18BDC2190095E648 /* ps2_ikm.c in Sources */, 834FE0B8215C798C000A5D3D /* acm_decoder_decode.c in Sources */, 836F6FBD18BDC2190095E648 /* nwa.c in Sources */, 834FE0F7215C79ED000A5D3D /* vis.c in Sources */, @@ -2595,6 +2664,13 @@ "$(inherited)", BUILD_VGMSTREAM, "VAR_ARRAYS=1", + VGM_USE_ATRAC9, + VGM_USE_FFMPEG, + VGM_USE_G719, + VGM_USE_G7221, + VGM_USE_MPEG, + VGM_USE_VORBIS, + __MACOSX__, ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -2654,6 +2730,13 @@ BUILD_VGMSTREAM, "VAR_ARRAYS=1", "$(inherited)", + VGM_USE_ATRAC9, + VGM_USE_FFMPEG, + VGM_USE_G719, + VGM_USE_G7221, + VGM_USE_MPEG, + VGM_USE_VORBIS, + __MACOSX__, ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/atrac9_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/atrac9_decoder.c index 58594bb64..324e8b168 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/atrac9_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/atrac9_decoder.c @@ -1,7 +1,11 @@ #include "coding.h" #ifdef VGM_USE_ATRAC9 +#ifdef __MACOSX__ #include +#else +#include "libatrac9.h" +#endif /* opaque struct */ @@ -9,7 +13,7 @@ struct atrac9_codec_data { uint8_t *data_buffer; size_t data_buffer_size; - sample *sample_buffer; + sample_t *sample_buffer; size_t samples_filled; /* number of samples in the buffer */ size_t samples_used; /* number of samples extracted from the buffer */ @@ -39,7 +43,7 @@ atrac9_codec_data *init_atrac9(atrac9_config *cfg) { status = Atrac9GetCodecInfo(data->handle, &data->info); if (status < 0) goto fail; - //;VGM_LOG("ATRAC9: config=%x, sf-size=%x, sub-frames=%i x %i samples\n", cfg->config_data, info.superframeSize, info.framesInSuperframe, info.frameSamples); + //;VGM_LOG("ATRAC9: config=%x, sf-size=%x, sub-frames=%i x %i samples\n", cfg->config_data, data->info.superframeSize, data->info.framesInSuperframe, data->info.frameSamples); if (cfg->channels && cfg->channels != data->info.channels) { VGM_LOG("ATRAC9: channels in header %i vs config %i don't match\n", cfg->channels, data->info.channels); @@ -50,7 +54,7 @@ atrac9_codec_data *init_atrac9(atrac9_config *cfg) { /* must hold at least one superframe and its samples */ data->data_buffer_size = data->info.superframeSize; data->data_buffer = calloc(sizeof(uint8_t), data->data_buffer_size); - data->sample_buffer = calloc(sizeof(sample), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe); + data->sample_buffer = calloc(sizeof(sample_t), data->info.channels * data->info.frameSamples * data->info.framesInSuperframe); data->samples_to_discard = cfg->encoder_delay; @@ -63,7 +67,7 @@ fail: return NULL; } -void decode_atrac9(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, int channels) { +void decode_atrac9(VGMSTREAM *vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels) { VGMSTREAMCHANNEL *stream = &vgmstream->ch[0]; atrac9_codec_data * data = vgmstream->codec_data; int samples_done = 0; @@ -220,20 +224,20 @@ void free_atrac9(atrac9_codec_data *data) { } -size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data) { - return bytes / data->info.superframeSize * (data->info.frameSamples * data->info.framesInSuperframe); -} - -#if 0 //not needed (for now) -int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size) { +static int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size, size_t *out_samples_per_frame) { static const int sample_rate_table[16] = { 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 44100, 48000, 64000, 88200, 96000,128000,176400,192000 }; + static const int samples_power_table[16] = { + 6, 6, 7, 7, 7, 8, 8, 8, + 6, 6, 7, 7, 7, 8, 8, 8 + }; static const int channel_table[8] = { 1, 2, 2, 6, 8, 4, 0, 0 }; + int superframe_size, frames_per_superframe, samples_per_frame, samples_per_superframe; uint32_t sync = (atrac9_config >> 24) & 0xff; /* 8b */ uint8_t sample_rate_index = (atrac9_config >> 20) & 0x0f; /* 4b */ uint8_t channels_index = (atrac9_config >> 17) & 0x07; /* 3b */ @@ -242,6 +246,11 @@ int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_c size_t superframe_index = (atrac9_config >> 3) & 0x3; /* 2b */ /* uint8_t unused = (atrac9_config >> 0) & 0x7);*/ /* 3b */ + superframe_size = ((frame_size+1) << superframe_index); + frames_per_superframe = (1 << superframe_index); + samples_per_frame = 1 << samples_power_table[sample_rate_index]; + samples_per_superframe = samples_per_frame * frames_per_superframe; + if (sync != 0xFE) goto fail; if (out_sample_rate) @@ -249,11 +258,23 @@ int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_c if (out_channels) *out_channels = channel_table[channels_index]; if (out_frame_size) - *out_frame_size = (frame_size+1) * (1 << superframe_index); + *out_frame_size = superframe_size; + if (out_samples_per_frame) + *out_samples_per_frame = samples_per_superframe; return 1; fail: return 0; } -#endif + +size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data) { + return bytes / data->info.superframeSize * (data->info.frameSamples * data->info.framesInSuperframe); +} + +size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t atrac9_config) { + size_t frame_size, samples_per_frame; + if (!atrac9_parse_config(atrac9_config, NULL, NULL, &frame_size, &samples_per_frame)) + return 0; + return bytes / frame_size * samples_per_frame; +} #endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding.h b/Frameworks/vgmstream/vgmstream/src/coding/coding.h index 43f17e90a..532b6d85d 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/coding.h +++ b/Frameworks/vgmstream/vgmstream/src/coding/coding.h @@ -15,37 +15,38 @@ void decode_g721(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, void g72x_init_state(struct g72x_state *state_ptr); /* ima_decoder */ -void decode_standard_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo, int is_high_first); -void decode_3ds_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_snds_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void decode_otns_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void decode_wv6_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_alp_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_ffta2_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_standard_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo, int is_high_first); +void decode_3ds_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_snds_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_otns_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_wv6_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_alp_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_ffta2_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_blitz_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_ms_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel); -void decode_ref_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel); +void decode_ms_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel); +void decode_ref_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel); -void decode_xbox_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo); -void decode_xbox_ima_mch(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void decode_nds_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_dat4_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_rad_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel); -void decode_rad_ima_mono(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_apple_ima4(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_fsb_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void decode_wwise_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void decode_awc_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, uint16_t frame_format); +void decode_xbox_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo); +void decode_xbox_ima_mch(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_nds_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_dat4_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_rad_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel); +void decode_rad_ima_mono(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_apple_ima4(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_fsb_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_wwise_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_awc_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, uint16_t frame_format); size_t ima_bytes_to_samples(size_t bytes, int channels); size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels); size_t xbox_ima_bytes_to_samples(size_t bytes, int channels); size_t apple_ima4_bytes_to_samples(size_t bytes, int channels); /* ngc_dsp_decoder */ -void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_ngc_dsp_subint(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int interleave); +void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_ngc_dsp_subint(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int interleave); size_t dsp_bytes_to_samples(size_t bytes, int channels); int32_t dsp_nibbles_to_samples(int32_t nibbles); void dsp_read_coefs_be(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t offset, off_t spacing); @@ -62,27 +63,28 @@ void decode_ngc_dtk(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci void decode_ngc_afc(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); /* pcm_decoder */ -void decode_pcm16le(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_pcm16be(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_pcm16_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian); -void decode_pcm8(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_pcm8_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_pcm8_sb(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void decode_pcm4_unsigned(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void decode_ulaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_ulaw_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_alaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -void decode_pcmfloat(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian); +void decode_pcm16le(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_pcm16be(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_pcm16_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian); +void decode_pcm8(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_pcm8_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_pcm8_sb(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_pcm4_unsigned(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_ulaw(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_ulaw_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_alaw(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_pcmfloat(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian); size_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample); /* psx_decoder */ -void decode_psx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int is_badflags); -void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size); +void decode_psx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int is_badflags); +void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size); int ps_find_loop_offsets(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t * out_loop_start, int32_t * out_loop_end); int ps_find_loop_offsets_full(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, size_t interleave, int32_t * out_loop_start, int32_t * out_loop_end); +size_t ps_find_padding(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, size_t interleave, int discard_empty); size_t ps_bytes_to_samples(size_t bytes, int channels); size_t ps_cfg_bytes_to_samples(size_t bytes, size_t frame_size, int channels); @@ -122,17 +124,17 @@ void free_acm(acm_codec_data *data); void decode_nwa(NWAData *nwa, sample *outbuf, int32_t samples_to_do); /* msadpcm_decoder */ -void decode_msadpcm_stereo(VGMSTREAM * vgmstream, sample * outbuf, int32_t first_sample, int32_t samples_to_do); -void decode_msadpcm_mono(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void decode_msadpcm_ck(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_msadpcm_stereo(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t first_sample, int32_t samples_to_do); +void decode_msadpcm_mono(VGMSTREAM * vgmstream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_msadpcm_ck(VGMSTREAM * vgmstream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); long msadpcm_bytes_to_samples(long bytes, int block_size, int channels); /* yamaha_decoder */ -void decode_aica(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo); -void decode_yamaha(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); -void decode_yamaha_nxap(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); -size_t aica_bytes_to_samples(size_t bytes, int channels); +void decode_yamaha(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo); +void decode_aska(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_nxap(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); size_t yamaha_bytes_to_samples(size_t bytes, int channels); +size_t aska_bytes_to_samples(size_t bytes, int channels); /* nds_procyon_decoder */ void decode_nds_procyon(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); @@ -147,10 +149,10 @@ void decode_sassc(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing void decode_lsf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); /* mtaf_decoder */ -void decode_mtaf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_mtaf(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); /* mta2_decoder */ -void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_mta2(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); /* mc3_decoder */ void decode_mc3(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); @@ -162,7 +164,7 @@ void decode_fadpcm(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacin void decode_asf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); /* dsa_decoder */ -void decode_dsa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); +void decode_dsa(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); /* xmd_decoder */ void decode_xmd(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, size_t frame_size); @@ -174,8 +176,8 @@ void decode_derf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, void decode_circus_adpcm(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do); /* oki_decoder */ -void decode_pcfx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int mode); -void decode_oki16(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); +void decode_pcfx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int mode); +void decode_oki16(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel); size_t oki_bytes_to_samples(size_t bytes, int channels); /* ea_mt_decoder*/ @@ -191,20 +193,20 @@ void free_ea_mt(ea_mt_codec_data *data, int channels); hca_codec_data *init_hca(STREAMFILE *streamFile); void decode_hca(hca_codec_data * data, sample * outbuf, int32_t samples_to_do); void reset_hca(hca_codec_data * data); -void loop_hca(hca_codec_data * data); +void loop_hca(hca_codec_data * data, int32_t num_sample); void free_hca(hca_codec_data * data); int test_hca_key(hca_codec_data * data, unsigned long long keycode); #ifdef VGM_USE_VORBIS /* ogg_vorbis_decoder */ -void decode_ogg_vorbis(ogg_vorbis_codec_data * data, sample * outbuf, int32_t samples_to_do, int channels); +void decode_ogg_vorbis(ogg_vorbis_codec_data * data, sample_t * outbuf, int32_t samples_to_do, int channels); void reset_ogg_vorbis(VGMSTREAM *vgmstream); void seek_ogg_vorbis(VGMSTREAM *vgmstream, int32_t num_sample); void free_ogg_vorbis(ogg_vorbis_codec_data *data); /* vorbis_custom_decoder */ vorbis_custom_codec_data *init_vorbis_custom(STREAMFILE *streamfile, off_t start_offset, vorbis_custom_t type, vorbis_custom_config * config); -void decode_vorbis_custom(VGMSTREAM * vgmstream, sample * outbuf, int32_t samples_to_do, int channels); +void decode_vorbis_custom(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels); void reset_vorbis_custom(VGMSTREAM *vgmstream); void seek_vorbis_custom(VGMSTREAM *vgmstream, int32_t num_sample); void free_vorbis_custom(vorbis_custom_codec_data *data); @@ -214,7 +216,7 @@ void free_vorbis_custom(vorbis_custom_codec_data *data); /* mpeg_decoder */ mpeg_codec_data *init_mpeg(STREAMFILE *streamfile, off_t start_offset, coding_t *coding_type, int channels); mpeg_codec_data *init_mpeg_custom(STREAMFILE *streamFile, off_t start_offset, coding_t *coding_type, int channels, mpeg_custom_t custom_type, mpeg_custom_config *config); -void decode_mpeg(VGMSTREAM * vgmstream, sample * outbuf, int32_t samples_to_do, int channels); +void decode_mpeg(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels); void reset_mpeg(VGMSTREAM *vgmstream); void seek_mpeg(VGMSTREAM *vgmstream, int32_t num_sample); void free_mpeg(mpeg_codec_data *data); @@ -259,12 +261,12 @@ void free_at3plus(maiatrac3plus_codec_data *data); #ifdef VGM_USE_ATRAC9 /* atrac9_decoder */ atrac9_codec_data *init_atrac9(atrac9_config *cfg); -void decode_atrac9(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, int channels); +void decode_atrac9(VGMSTREAM *vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels); void reset_atrac9(VGMSTREAM *vgmstream); void seek_atrac9(VGMSTREAM *vgmstream, int32_t num_sample); void free_atrac9(atrac9_codec_data *data); size_t atrac9_bytes_to_samples(size_t bytes, atrac9_codec_data *data); -//int atrac9_parse_config(uint32_t atrac9_config, int *out_sample_rate, int *out_channels, size_t *out_frame_size); +size_t atrac9_bytes_to_samples_cfg(size_t bytes, uint32_t atrac9_config); #endif #ifdef VGM_USE_CELT @@ -282,12 +284,14 @@ ffmpeg_codec_data *init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, ui ffmpeg_codec_data *init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size); ffmpeg_codec_data *init_ffmpeg_header_offset_subsong(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size, int target_subsong); -void decode_ffmpeg(VGMSTREAM *stream, sample * outbuf, int32_t samples_to_do, int channels); +void decode_ffmpeg(VGMSTREAM *stream, sample_t * outbuf, int32_t samples_to_do, int channels); void reset_ffmpeg(VGMSTREAM *vgmstream); void seek_ffmpeg(VGMSTREAM *vgmstream, int32_t num_sample); void free_ffmpeg(ffmpeg_codec_data *data); void ffmpeg_set_skip_samples(ffmpeg_codec_data * data, int skip_samples); +uint32_t ffmpeg_get_channel_layout(ffmpeg_codec_data * data); +void ffmpeg_set_channel_remapping(ffmpeg_codec_data * data, int *channels_remap); /* ffmpeg_decoder_custom_opus.c (helper-things) */ ffmpeg_codec_data * init_ffmpeg_switch_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate); @@ -295,7 +299,7 @@ ffmpeg_codec_data * init_ffmpeg_ue4_opus(STREAMFILE *streamFile, off_t start_off ffmpeg_codec_data * init_ffmpeg_ea_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate); ffmpeg_codec_data * init_ffmpeg_x_opus(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, int skip, int sample_rate); -size_t switch_opus_get_samples(off_t offset, size_t data_size, STREAMFILE *streamFile); +size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *streamFile); size_t switch_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile); size_t ue4_opus_get_encoder_delay(off_t offset, STREAMFILE *streamFile); @@ -351,7 +355,9 @@ int riff_get_fact_skip_samples(STREAMFILE * streamFile, off_t start_offset); size_t atrac3_bytes_to_samples(size_t bytes, int full_block_align); size_t atrac3plus_bytes_to_samples(size_t bytes, int full_block_align); - +size_t ac3_bytes_to_samples(size_t bytes, int full_block_align, int channels); +size_t aac_get_samples(STREAMFILE *streamFile, off_t start_offset, size_t bytes); +size_t mpeg_get_samples(STREAMFILE *streamFile, off_t start_offset, size_t bytes); /* An internal struct to pass around and simulate a bitstream. */ diff --git a/Frameworks/vgmstream/vgmstream/src/coding/coding_utils.c b/Frameworks/vgmstream/vgmstream/src/coding/coding_utils.c index 2b464a359..6201d6bad 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/coding_utils.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/coding_utils.c @@ -1006,17 +1006,61 @@ fail: /* ******************************************** */ size_t atrac3_bytes_to_samples(size_t bytes, int full_block_align) { + if (full_block_align <= 0) return 0; /* ATRAC3 expects full block align since as is can mix joint stereo with mono blocks; * so (full_block_align / channels) DOESN'T give the size of a single channel (uncommon in ATRAC3 though) */ return (bytes / full_block_align) * 1024; } size_t atrac3plus_bytes_to_samples(size_t bytes, int full_block_align) { + if (full_block_align <= 0) return 0; /* ATRAC3plus expects full block align since as is can mix joint stereo with mono blocks; * so (full_block_align / channels) DOESN'T give the size of a single channel (common in ATRAC3plus) */ return (bytes / full_block_align) * 2048; } +size_t ac3_bytes_to_samples(size_t bytes, int full_block_align, int channels) { + if (full_block_align <= 0) return 0; + return (bytes / full_block_align) * 256 * channels; +} + + +size_t aac_get_samples(STREAMFILE *streamFile, off_t start_offset, size_t bytes) { + const int samples_per_frame = 1024; /* theoretically 960 exists in .MP4 so may need a flag */ + int frames = 0; + off_t offset = start_offset; + off_t max_offset = start_offset + bytes; + + if (!streamFile) + return 0; + + if (max_offset > get_streamfile_size(streamFile)) + max_offset = get_streamfile_size(streamFile); + + /* AAC sometimes comes with an "ADIF" header right before data but probably not in games, + * while standard raw frame headers are called "ADTS" and are similar to MPEG's: + * (see https://wiki.multimedia.cx/index.php/ADTS) */ + + /* AAC uses VBR so must read all frames */ + while (offset < max_offset) { + uint16_t frame_sync = read_u16be(offset+0x00, streamFile); + uint32_t frame_size = read_u32be(offset+0x02, streamFile); + + frame_sync = (frame_sync >> 4) & 0x0FFF; /* 12b */ + frame_size = (frame_size >> 5) & 0x1FFF; /* 13b */ + + if (frame_sync != 0xFFF) + break; + if (frame_size <= 0x08) + break; + + frames++; + offset += frame_size; + } + + return frames * samples_per_frame; +} + /* ******************************************** */ /* BITSTREAM */ diff --git a/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c index 6f4f12943..74e256128 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/dsa_decoder.c @@ -10,7 +10,7 @@ static const int dsa_coefs[16] = { /* Decodes Ocean DSA ADPCM codec from Last Rites (PC). * Reverse engineered from daemon1's reverse engineering. */ -void decode_dsa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_dsa(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { off_t frame_offset; int i, frames_in, sample_count = 0; size_t bytes_per_frame, samples_per_frame; @@ -32,20 +32,20 @@ void decode_dsa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, /* decode nibbles */ for (i = first_sample; i < first_sample + samples_to_do; i++) { - int32_t new_sample; + int32_t sample; uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x01 + i/2,stream->streamfile); - new_sample = i&1 ? /* high nibble first */ + sample = i&1 ? /* high nibble first */ (nibbles >> 0) & 0xf : (nibbles >> 4) & 0xf; - new_sample = ((int16_t)(new_sample << 0xC) >> shift); /* 16b sign extend + scale */ - new_sample = new_sample + ((hist1 * filter) >> 0x10); + sample = ((int16_t)(sample << 0xC) >> shift); /* 16b sign extend + scale */ + sample = sample + ((hist1 * filter) >> 0x10); - outbuf[sample_count] = (int16_t)(new_sample << 2); + outbuf[sample_count] = (sample_t)(sample << 2); sample_count += channelspacing; - hist1 = new_sample; + hist1 = sample; } stream->adpcm_history1_32 = hist1; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c index 90a6c15e4..a2c5b1402 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder.c @@ -28,8 +28,27 @@ static void g_init_ffmpeg() { } } +static void remap_audio(sample_t *outbuf, int sample_count, int channels, int channel_mappings[]) { + int ch_from,ch_to,s; + sample_t temp; + for (s = 0; s < sample_count; s++) { + for (ch_from = 0; ch_from < channels; ch_from++) { + if (ch_from > 32) + continue; + + ch_to = channel_mappings[ch_from]; + if (ch_to < 1 || ch_to > 32 || ch_to > channels-1 || ch_from == ch_to) + continue; + + temp = outbuf[s*channels + ch_from]; + outbuf[s*channels + ch_from] = outbuf[s*channels + ch_to]; + outbuf[s*channels + ch_to] = temp; + } + } +} + /* converts codec's samples (can be in any format, ex. Ogg's float32) to PCM16 */ -static void convert_audio_pcm16(sample *outbuf, const uint8_t *inbuf, int fullSampleCount, int bitsPerSample, int floatingPoint) { +static void convert_audio_pcm16(sample_t *outbuf, const uint8_t *inbuf, int fullSampleCount, int bitsPerSample, int floatingPoint) { int s; switch (bitsPerSample) { case 8: { @@ -495,7 +514,7 @@ fail: } /* decode samples of any kind of FFmpeg format */ -void decode_ffmpeg(VGMSTREAM *vgmstream, sample * outbuf, int32_t samples_to_do, int channels) { +void decode_ffmpeg(VGMSTREAM *vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels) { ffmpeg_codec_data *data = vgmstream->codec_data; int samplesReadNow; //todo use either channels / data->channels / codecCtx->channels @@ -656,6 +675,8 @@ end: /* convert native sample format into PCM16 outbuf */ samplesReadNow = bytesRead / (bytesPerSample * channels); convert_audio_pcm16(outbuf, data->sampleBuffer, samplesReadNow * channels, data->bitsPerSample, data->floatingPoint); + if (data->channel_remap_set) + remap_audio(outbuf, samplesReadNow, data->channels, data->channel_remap); /* clean buffer when requested more samples than possible */ if (endOfAudio && samplesReadNow < samples_to_do) { @@ -808,4 +829,25 @@ void ffmpeg_set_skip_samples(ffmpeg_codec_data * data, int skip_samples) { data->skipSamples = skip_samples; } +/* returns channel layout if set */ +uint32_t ffmpeg_get_channel_layout(ffmpeg_codec_data * data) { + if (!data || !data->codecCtx) return 0; + return (uint32_t)data->codecCtx->channel_layout; /* uint64 but there ain't so many speaker mappings */ +} + +/* yet another hack to fix codecs that encode channels in different order and reorder on decoder + * but FFmpeg doesn't do it automatically + * (maybe should be done via mixing, but could clash with other stuff?) */ +void ffmpeg_set_channel_remapping(ffmpeg_codec_data * data, int *channel_remap) { + int i; + + if (data->channels > 32) + return; + + for (i = 0; i < data->channels; i++) { + data->channel_remap[i] = channel_remap[i]; + } + data->channel_remap_set = 1; +} + #endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c index 92e0513d4..8f992b863 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ffmpeg_decoder_custom_opus.c @@ -523,9 +523,9 @@ static size_t get_xopus_packet_size(int packet, STREAMFILE * streamfile) { #ifdef VGM_USE_FFMPEG -static size_t custom_opus_get_samples(off_t offset, size_t data_size, STREAMFILE *streamFile, opus_type_t type) { +static size_t custom_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *streamFile, opus_type_t type) { size_t num_samples = 0; - off_t end_offset = offset + data_size; + off_t end_offset = offset + stream_size; int packet = 0; if (end_offset > get_streamfile_size(streamFile)) { @@ -569,8 +569,8 @@ static size_t custom_opus_get_samples(off_t offset, size_t data_size, STREAMFILE return num_samples; } -size_t switch_opus_get_samples(off_t offset, size_t data_size, STREAMFILE *streamFile) { - return custom_opus_get_samples(offset, data_size, streamFile, OPUS_SWITCH); +size_t switch_opus_get_samples(off_t offset, size_t stream_size, STREAMFILE *streamFile) { + return custom_opus_get_samples(offset, stream_size, streamFile, OPUS_SWITCH); } diff --git a/Frameworks/vgmstream/vgmstream/src/coding/g719_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/g719_decoder.c index 3ff8d717d..c2466630c 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/g719_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/g719_decoder.c @@ -1,7 +1,12 @@ #include "coding.h" #ifdef VGM_USE_G719 +#ifdef __MACOSX__ #include +#else +#include +#endif + #define G719_MAX_CODES ((1280/8)) /* in int16, so max frame size is (value/8)*2 (0xF0=common, 0x140=decoder max 2560b, rare) */ diff --git a/Frameworks/vgmstream/vgmstream/src/coding/hca_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/hca_decoder.c index 88a3e6479..64b6cd477 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/hca_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/hca_decoder.c @@ -130,9 +130,19 @@ void reset_hca(hca_codec_data * data) { data->samples_to_discard = data->info.encoderDelay; } -void loop_hca(hca_codec_data * data) { +void loop_hca(hca_codec_data * data, int32_t num_sample) { if (!data) return; + /* manually calc loop values if not set (should only happen with installed/forced looping, + * as actual files usually pad encoder delay so earliest loopStartBlock becomes 1-2, + * probably for decoding cleanup so this may not be as exact) */ + if (data->info.loopStartBlock == 0 && data->info.loopStartDelay == 0) { + int target_sample = num_sample + data->info.encoderDelay; + + data->info.loopStartBlock = target_sample / data->info.samplesPerBlock; + data->info.loopStartDelay = target_sample - (data->info.loopStartBlock * data->info.samplesPerBlock); + } + data->current_block = data->info.loopStartBlock; data->samples_filled = 0; data->samples_consumed = 0; @@ -155,9 +165,10 @@ void free_hca(hca_codec_data * data) { #define HCA_KEY_SCORE_SCALE 10 /* ignores beginning frames (~10 is not uncommon, Dragalia Lost vocal layers have lots) */ #define HCA_KEY_MAX_SKIP_BLANKS 1200 -/* 5~15 should be enough, but almost silent or badly mastered files may need tweaks */ -#define HCA_KEY_MIN_TEST_FRAMES 5 -#define HCA_KEY_MAX_TEST_FRAMES 10 +/* 5~15 should be enough, but almost silent or badly mastered files may need tweaks + * (ex. newer Tales of the Rays files clip a lot and need +6 as some keys give almost-ok results) */ +#define HCA_KEY_MIN_TEST_FRAMES 7 +#define HCA_KEY_MAX_TEST_FRAMES 12 /* score of 10~30 isn't uncommon in a single frame, too many frames over that is unlikely */ #define HCA_KEY_MAX_FRAME_SCORE 150 #define HCA_KEY_MAX_TOTAL_SCORE (HCA_KEY_MAX_TEST_FRAMES * 50*HCA_KEY_SCORE_SCALE) diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c index b43094a21..fe56cd62d 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ima_decoder.c @@ -236,6 +236,33 @@ static void ffta2_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset if (*step_index > 88) *step_index=88; } +/* Yet another IMA expansion, from the exe */ +static void blitz_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, int nibble_shift, int32_t * hist1, int32_t * step_index) { + int sample_nibble, sample_decoded, step, delta; + + sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf; /* ADPCM code */ + sample_decoded = *hist1; /* predictor value */ + step = ADPCMTable[*step_index]; /* current step */ + + /* table has 2 different values, not enough to bother adding the full table */ + if (step == 22385) + step = 22358; + else if (step == 24623) + step = 24633; + + delta = (sample_nibble & 0x07); + if (sample_nibble & 8) delta = -delta; + delta = (step >> 1) + delta * step; /* custom */ + sample_decoded += delta; + + /* somehow the exe tries to clamp hist, but actually doesn't (bug?), + * not sure if pcm buffer would be clamped outside though */ + *hist1 = sample_decoded;//clamp16(sample_decoded); + *step_index += IMA_IndexTable[sample_nibble]; + if (*step_index < 0) *step_index=0; + if (*step_index > 88) *step_index=88; +} + /* ************************************ */ /* DVI/IMA */ /* ************************************ */ @@ -243,7 +270,7 @@ static void ffta2_ima_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset /* Standard DVI/IMA ADPCM (as in, ADPCM recommended by the IMA using Intel/DVI's implementation). * Configurable: stereo or mono/interleave nibbles, and high or low nibble first. * For vgmstream, low nibble is called "IMA ADPCM" and high nibble is "DVI IMA ADPCM" (same thing though). */ -void decode_standard_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo, int is_high_first) { +void decode_standard_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo, int is_high_first) { int i, sample_count = 0; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -271,7 +298,7 @@ void decode_standard_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channel stream->adpcm_step_index = step_index; } -void decode_3ds_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_3ds_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -292,7 +319,7 @@ void decode_3ds_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci stream->adpcm_step_index = step_index; } -void decode_snds_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_snds_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, sample_count; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -313,7 +340,7 @@ void decode_snds_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspac stream->adpcm_step_index = step_index; } -void decode_otns_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_otns_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, sample_count; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -337,7 +364,7 @@ void decode_otns_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * } /* WV6 IMA, DVI IMA with custom nibble expand */ -void decode_wv6_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_wv6_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -359,7 +386,7 @@ void decode_wv6_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci } /* ALT IMA, DVI IMA with custom nibble expand */ -void decode_alp_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_alp_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -381,7 +408,7 @@ void decode_alp_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci } /* FFTA2 IMA, DVI IMA with custom nibble expand/rounding */ -void decode_ffta2_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_ffta2_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -403,6 +430,28 @@ void decode_ffta2_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa stream->adpcm_step_index = step_index; } +/* Blitz IMA, IMA with custom nibble expand */ +void decode_blitz_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { + int i, sample_count; + int32_t hist1 = stream->adpcm_history1_32; + int step_index = stream->adpcm_step_index; + + //external interleave + + //no header + + for (i=first_sample,sample_count=0; ioffset + i/2; + int nibble_shift = (i&1?4:0); //low nibble first + + blitz_ima_expand_nibble(stream, byte_offset,nibble_shift, &hist1, &step_index); + outbuf[sample_count] = (short)(hist1); + } + + stream->adpcm_history1_32 = hist1; + stream->adpcm_step_index = step_index; +} + /* ************************************ */ /* MS-IMA */ /* ************************************ */ @@ -410,7 +459,7 @@ void decode_ffta2_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa /* IMA with custom frame sizes, header and nibble layout. Outputs an odd number of samples per frame, * so to simplify calcs this decodes full frames, thus hist doesn't need to be mantained. * Officially defined in "Microsoft Multimedia Standards Update" doc (RIFFNEW.pdf). */ -void decode_ms_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_ms_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, samples_read = 0, samples_done = 0, max_samples; int32_t hist1;// = stream->adpcm_history1_32; int step_index;// = stream->adpcm_step_index; @@ -464,7 +513,7 @@ void decode_ms_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * ou } /* Reflection's MS-IMA with custom nibble layout (some info from XA2WAV by Deniz Oezmen) */ -void decode_ref_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_ref_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, samples_read = 0, samples_done = 0, max_samples; int32_t hist1;// = stream->adpcm_history1_32; int step_index;// = stream->adpcm_step_index; @@ -524,7 +573,7 @@ void decode_ref_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * o /* MS-IMA with fixed frame size, and outputs an even number of samples per frame (skips last nibble). * Defined in Xbox's SDK. Usable in mono or stereo modes (both suitable for interleaved multichannel). */ -void decode_xbox_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo) { +void decode_xbox_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo) { int i, frames_in, sample_pos = 0, block_samples, frame_size; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -576,7 +625,7 @@ void decode_xbox_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspac } /* Multichannel XBOX-IMA ADPCM, with all channels mixed in the same block (equivalent to multichannel MS-IMA; seen in .rsd XADP). */ -void decode_xbox_ima_mch(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_xbox_ima_mch(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, sample_count = 0, num_frame; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -622,7 +671,7 @@ void decode_xbox_ima_mch(VGMSTREAMCHANNEL * stream, sample * outbuf, int channel /* Similar to MS-IMA with even number of samples, header sample is not written (setup only). * Apparently clamps to -32767 unlike standard's -32768 (probably not noticeable). * Info here: http://problemkaputt.de/gbatek.htm#dssoundnotes */ -void decode_nds_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_nds_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -653,7 +702,7 @@ void decode_nds_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci stream->adpcm_step_index = step_index; } -void decode_dat4_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_dat4_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count; int32_t hist1 = stream->adpcm_history1_16;//todo unneeded 16? int step_index = stream->adpcm_step_index; @@ -682,7 +731,7 @@ void decode_dat4_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspac stream->adpcm_step_index = step_index; } -void decode_rad_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel) { +void decode_rad_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel) { int i, sample_count; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -716,7 +765,7 @@ void decode_rad_ima(VGMSTREAM * vgmstream,VGMSTREAMCHANNEL * stream, sample * ou stream->adpcm_step_index = step_index; } -void decode_rad_ima_mono(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_rad_ima_mono(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -748,7 +797,7 @@ void decode_rad_ima_mono(VGMSTREAMCHANNEL * stream, sample * outbuf, int channel } /* Apple's IMA4, a.k.a QuickTime IMA. 2 byte header and header sample is not written (setup only). */ -void decode_apple_ima4(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_apple_ima4(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count, num_frame; int16_t hist1 = stream->adpcm_history1_16;//todo unneeded 16? int step_index = stream->adpcm_step_index; @@ -781,7 +830,7 @@ void decode_apple_ima4(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelsp } /* XBOX-IMA with modified data layout */ -void decode_fsb_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel) { +void decode_fsb_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel) { int i, sample_count = 0; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -830,7 +879,7 @@ void decode_fsb_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * o } /* mono XBOX-IMA with header endianness and alt nibble expand (per hcs's decompilation) */ -void decode_wwise_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_wwise_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, sample_count = 0, num_frame; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -886,7 +935,7 @@ void decode_wwise_ima(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * */ /* MS-IMA with possibly the XBOX-IMA model of even number of samples per block (more tests are needed) */ -void decode_awc_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_awc_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count; int32_t hist1 = stream->adpcm_history1_32; @@ -923,7 +972,7 @@ void decode_awc_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci /* DVI stereo/mono with some mini header and sample output */ -void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, sample_count = 0; int32_t hist1 = stream->adpcm_history1_32; @@ -948,8 +997,8 @@ void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci offset += 0x10 + 0x08; if (version >= 3) offset += 0x04; - //if (version >= 6) /* supposedly this exists, maybe in later BAOs */ - // offset += 0x08; + if (version >= 6) /* later BAOs */ + offset += 0x08; /* write PCM samples, must be written to match header's num_samples (hist mustn't) */ max_samples_to_do = ((samples_to_do > header_samples) ? header_samples : samples_to_do); @@ -989,7 +1038,7 @@ void decode_ubi_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci /* IMA with variable frame formats controlled by the block layout. The original code uses * tables mapping all standard IMA combinations (to optimize calculations), but decodes the same. * Based on HCS's and Nisto's reverse engineering in h4m_audio_decode. */ -void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, uint16_t frame_format) { +void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, uint16_t frame_format) { int i, samples_done = 0; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -1062,11 +1111,13 @@ void decode_h4m_ima(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci /* ************************************************************* */ size_t ima_bytes_to_samples(size_t bytes, int channels) { + if (channels <= 0) return 0; /* 2 samples per byte (2 nibbles) in stereo or mono config */ return bytes * 2 / channels; } size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels) { + if (block_align <= 0 || channels <= 0) return 0; /* MS-IMA blocks have a 4 byte header per channel; 2 samples per byte (2 nibbles) */ return (bytes / block_align) * ((block_align - 0x04*channels) * 2 / channels + 1) + ((bytes % block_align) ? (((bytes % block_align) - 0x04*channels) * 2 / channels + 1) : 0); @@ -1074,6 +1125,7 @@ size_t ms_ima_bytes_to_samples(size_t bytes, int block_align, int channels) { size_t xbox_ima_bytes_to_samples(size_t bytes, int channels) { int block_align = 0x24 * channels; + if (channels <= 0) return 0; /* XBOX IMA blocks have a 4 byte header per channel; 2 samples per byte (2 nibbles) */ return (bytes / block_align) * (block_align - 4 * channels) * 2 / channels + ((bytes % block_align) ? ((bytes % block_align) - 4 * channels) * 2 / channels : 0); /* unlikely (encoder aligns) */ @@ -1081,6 +1133,7 @@ size_t xbox_ima_bytes_to_samples(size_t bytes, int channels) { size_t apple_ima4_bytes_to_samples(size_t bytes, int channels) { int block_align = 0x22 * channels; + if (channels <= 0) return 0; return (bytes / block_align) * (block_align - 0x02*channels) * 2 / channels + ((bytes % block_align) ? ((bytes % block_align) - 0x02*channels) * 2 / channels : 0); } diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c index a7a6b817f..f0d62fa34 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils.c @@ -78,12 +78,14 @@ int mpeg_custom_setup_init_default(STREAMFILE *streamFile, off_t start_offset, m //todo: test more: this improves the output, but seems formats aren't usually prepared // (and/or the num_samples includes all possible samples in file, so by discarding some it'll reach EOF) - // FSBs (with FMOD DLLs) don't seem to need it, even when files contain garbage at the beginning #if 0 /* set encoder delay (samples to skip at the beginning of a stream) if needed, which varies with encoder used */ switch(data->type) { case MPEG_AHX: data->skip_samples = 480; break; /* observed default */ case MPEG_P3D: data->skip_samples = info.frame_samples; break; /* matches Radical ADPCM (PC) output */ + /* FSBs (with FMOD DLLs) don't seem to need it. Particularly a few games (all from Wayforward?) + * contain audible garbage at the beginning, but it's actually there in-game too */ + case MPEG_FSB: data->skip_samples = 0; default: break; } data->samples_to_discard = data->skip_samples; @@ -300,5 +302,64 @@ fail: return 0; } +size_t mpeg_get_samples(STREAMFILE *streamFile, off_t start_offset, size_t bytes) { + off_t offset = start_offset; + off_t max_offset = start_offset + bytes; + int samples = 0; + mpeg_frame_info info; + size_t prev_size = 0; + int cbr_count = 0; + int is_vbr = 0; + + if (!streamFile) + return 0; + + if (max_offset > get_streamfile_size(streamFile)) + max_offset = get_streamfile_size(streamFile); + + /* MPEG may use VBR so must read all frames */ + while (offset < max_offset) { + + /* skip ID3v2 */ + if ((read_32bitBE(offset+0x00, streamFile) & 0xFFFFFF00) == 0x49443300) { /* "ID3\0" */ + size_t frame_size = 0; + uint8_t flags = read_8bit(offset+0x05, streamFile); + /* this is how it's officially read :/ */ + frame_size += read_8bit(offset+0x06, streamFile) << 21; + frame_size += read_8bit(offset+0x07, streamFile) << 14; + frame_size += read_8bit(offset+0x08, streamFile) << 7; + frame_size += read_8bit(offset+0x09, streamFile) << 0; + frame_size += 0x0a; + if (flags & 0x10) /* footer? */ + frame_size += 0x0a; + + offset += frame_size; + continue; + } + + /* this may fail with unknown ID3 tags */ + if (!mpeg_get_frame_info(streamFile, offset, &info)) + break; + + if (prev_size && prev_size != info.frame_size) { + is_vbr = 1; + } + else if (!is_vbr) { + cbr_count++; + } + + if (cbr_count >= 10) { + /* must be CBR, don't bother counting */ + samples = (bytes / info.frame_size) * info.frame_samples; + break; + } + + offset += info.frame_size; + prev_size = info.frame_size; + samples += info.frame_samples; /* header frames may be 0? */ + } + + return samples; +} #endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c index fa1f92daa..399497c76 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_custom_utils_ealayer3.c @@ -506,7 +506,6 @@ static int ealayer3_rebuild_mpeg_frame(vgm_bitstream* is_0, ealayer3_frame_info* is_0->b_off = eaf_0->data_offset_b; for (i = 0; i < eaf_0->channels; i++) { /* granule0 */ for (j = 0; j < eaf_0->main_data_size[i]; j++) { - uint32_t c = 0; r_bits(is_0, 1, &c); w_bits(os, 1, c); } diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c index 8093f0053..e8db73459 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mpeg_decoder.c @@ -3,15 +3,19 @@ #include "../vgmstream.h" #ifdef VGM_USE_MPEG +#ifdef __MACOSX__ #include +#else +#include +#endif #include "mpeg_decoder.h" #define MPEG_DATA_BUFFER_SIZE 0x1000 /* at least one MPEG frame (max ~0x5A1 plus some more in case of free bitrate) */ static mpg123_handle * init_mpg123_handle(); -static void decode_mpeg_standard(VGMSTREAMCHANNEL *stream, mpeg_codec_data * data, sample * outbuf, int32_t samples_to_do, int channels); -static void decode_mpeg_custom(VGMSTREAM * vgmstream, mpeg_codec_data * data, sample * outbuf, int32_t samples_to_do, int channels); +static void decode_mpeg_standard(VGMSTREAMCHANNEL *stream, mpeg_codec_data * data, sample_t * outbuf, int32_t samples_to_do, int channels); +static void decode_mpeg_custom(VGMSTREAM * vgmstream, mpeg_codec_data * data, sample_t * outbuf, int32_t samples_to_do, int channels); static void decode_mpeg_custom_stream(VGMSTREAMCHANNEL *stream, mpeg_codec_data * data, int num_stream); @@ -218,7 +222,7 @@ fail: /* DECODERS */ /************/ -void decode_mpeg(VGMSTREAM * vgmstream, sample * outbuf, int32_t samples_to_do, int channels) { +void decode_mpeg(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels) { mpeg_codec_data * data = (mpeg_codec_data *) vgmstream->codec_data; if (!data->custom) { @@ -232,7 +236,7 @@ void decode_mpeg(VGMSTREAM * vgmstream, sample * outbuf, int32_t samples_to_do, * Decode anything mpg123 can. * Feeds raw data and extracts decoded samples as needed. */ -static void decode_mpeg_standard(VGMSTREAMCHANNEL *stream, mpeg_codec_data * data, sample * outbuf, int32_t samples_to_do, int channels) { +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; mpg123_handle *m = data->m; @@ -290,7 +294,7 @@ static void decode_mpeg_standard(VGMSTREAMCHANNEL *stream, mpeg_codec_data * dat * Copies to outbuf when there are samples in all streams and calls decode_mpeg_custom_stream to decode. . 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 * outbuf, int32_t samples_to_do, int channels) { +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; while (samples_done < samples_to_do) { @@ -327,7 +331,7 @@ static void decode_mpeg_custom(VGMSTREAM * vgmstream, mpeg_codec_data * data, sa ch = 0; for (stream = 0; stream < data->streams_size; stream++) { mpeg_custom_stream *ms = data->streams[stream]; - sample* inbuf = (sample*)ms->output_buffer; + sample_t *inbuf = (sample_t *)ms->output_buffer; int stream_channels = ms->channels_per_frame; int stream_ch, s; @@ -509,42 +513,37 @@ void free_mpeg(mpeg_codec_data *data) { /* seeks stream to 0 */ void reset_mpeg(VGMSTREAM *vgmstream) { - off_t input_offset; mpeg_codec_data *data = vgmstream->codec_data; if (!data) return; + flush_mpeg(data); +#if 0 + /* flush_mpeg properly resets mpg123 with mpg123_open_feed, and + * offsets are reset in the VGMSTREAM externally, but for posterity: */ if (!data->custom) { + off_t input_offset = 0; mpg123_feedseek(data->m,0,SEEK_SET,&input_offset); - /* input_offset is ignored as we can assume it will be 0 for a seek to sample 0 */ } else { + off_t input_offset = 0; int i; - /* re-start from 0 */ for (i = 0; i < data->streams_size; i++) { mpg123_feedseek(data->streams[i]->m,0,SEEK_SET,&input_offset); - data->streams[i]->bytes_in_buffer = 0; - data->streams[i]->buffer_full = 0; - data->streams[i]->buffer_used = 0; - data->streams[i]->samples_filled = 0; - data->streams[i]->samples_used = 0; - data->streams[i]->current_size_count = 0; - data->streams[i]->current_size_target = 0; - data->streams[i]->decode_to_discard = 0; } - - data->samples_to_discard = data->skip_samples; /* initial delay */ } +#endif } /* seeks to a point */ void seek_mpeg(VGMSTREAM *vgmstream, int32_t num_sample) { - off_t input_offset; mpeg_codec_data *data = vgmstream->codec_data; if (!data) return; if (!data->custom) { + off_t input_offset = 0; + mpg123_feedseek(data->m, num_sample,SEEK_SET,&input_offset); /* adjust loop with mpg123's offset (useful?) */ @@ -553,31 +552,20 @@ void seek_mpeg(VGMSTREAM *vgmstream, int32_t num_sample) { } else { int i; - /* re-start from 0 */ + + flush_mpeg(data); + + /* restart from 0 and manually discard samples, since we don't really know the correct offset */ for (i = 0; i < data->streams_size; i++) { - mpg123_feedseek(data->streams[i]->m,0,SEEK_SET,&input_offset); - data->streams[i]->bytes_in_buffer = 0; - data->streams[i]->buffer_full = 0; - data->streams[i]->buffer_used = 0; - data->streams[i]->samples_filled = 0; - data->streams[i]->samples_used = 0; - data->streams[i]->current_size_count = 0; - data->streams[i]->current_size_target = 0; - data->streams[i]->decode_to_discard = 0; + //mpg123_feedseek(data->streams[i]->m,0,SEEK_SET,&input_offset); /* already reset */ /* force first offset as discard-looping needs to start from the beginning */ if (vgmstream->loop_ch) vgmstream->loop_ch[i].offset = vgmstream->loop_ch[i].channel_start_offset; } - /* manually discard samples, since we don't really know the correct offset */ - data->samples_to_discard = num_sample; - data->samples_to_discard += data->skip_samples; + data->samples_to_discard += num_sample; } - - data->bytes_in_buffer = 0; - data->buffer_full = 0; - data->buffer_used = 0; } /* resets mpg123 decoder and its internals without seeking, useful when a new MPEG substream starts */ diff --git a/Frameworks/vgmstream/vgmstream/src/coding/msadpcm_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/msadpcm_decoder.c index 646027767..1f48830ab 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/msadpcm_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/msadpcm_decoder.c @@ -19,7 +19,7 @@ static const int msadpcm_coefs[7][2] = { { 392, -232 } }; -void decode_msadpcm_stereo(VGMSTREAM * vgmstream, sample * outbuf, int32_t first_sample, int32_t samples_to_do) { +void decode_msadpcm_stereo(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t first_sample, int32_t samples_to_do) { VGMSTREAMCHANNEL *ch1,*ch2; STREAMFILE *streamfile; int i, frames_in; @@ -97,7 +97,7 @@ void decode_msadpcm_stereo(VGMSTREAM * vgmstream, sample * outbuf, int32_t first } } -void decode_msadpcm_mono(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_msadpcm_mono(VGMSTREAM * vgmstream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { VGMSTREAMCHANNEL *stream = &vgmstream->ch[channel]; int i, frames_in; size_t bytes_per_frame, samples_per_frame; @@ -160,7 +160,7 @@ void decode_msadpcm_mono(VGMSTREAM * vgmstream, sample * outbuf, int channelspac /* Cricket Audio's MSADPCM, same thing with reversed hist and nibble order * (their tools may convert to float/others but internally it's all PCM16, from debugging). */ -void decode_msadpcm_ck(VGMSTREAM * vgmstream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_msadpcm_ck(VGMSTREAM * vgmstream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { VGMSTREAMCHANNEL *stream = &vgmstream->ch[channel]; int i, frames_in; size_t bytes_per_frame, samples_per_frame; @@ -222,6 +222,7 @@ void decode_msadpcm_ck(VGMSTREAM * vgmstream, sample * outbuf, int channelspacin } long msadpcm_bytes_to_samples(long bytes, int block_size, int channels) { + if (block_size <= 0 || channels <= 0) return 0; return (bytes / block_size) * (block_size - (7-1)*channels) * 2 / channels + ((bytes % block_size) ? ((bytes % block_size) - (7-1)*channels) * 2 / channels : 0); } diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mta2_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/mta2_decoder.c index 2ecbb3279..06480c6da 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mta2_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mta2_decoder.c @@ -1,7 +1,7 @@ #include "coding.h" #include "../util.h" -/* MTA2 (EA XAS variant?) decoder based on: +/* MTA2 decoder based on: * - MGS Developer Wiki: https://www.mgsdevwiki.com/wiki/index.php/MTA2_(Codec) [codec by daemon1] * - Solid4 tools: https://github.com/GHzGangster/Drebin * @@ -11,23 +11,21 @@ * * up to 16 possible tracks, but max seen is 3 (ex. track0=sneaking, track1=action, track2=ambience) * - each ch frame is divided into 4 headers + 4 vertical groups with nibbles (0x4*4 + 0x20*4) * ex. group1 is 0x04(4) + 0x14(4) + 0x24(4) + 0x34(4) ... (vertically maybe for paralelism?) - * - in case of "macroblock" layout, there are also headers before N tracks (like other MGS games) * * Due to this vertical layout and multiple hist/indexes, it decodes everything in a block between calls * but discards unwanted data, instead of trying to skip to the target nibble. Meaning no need to save hist, and * expects samples_to_do to be block_samples at most (could be simplified, I guess). - * - * Because of how the macroblock/track and stream's offset per channel work, they are supported by - * autodetecting and skipping when needed (ideally should keep a special layout/count, but this is simpler). */ -static const int c1[8] = { /* mod table 1 */ +/* coefs table (extended XA filters) */ +static const int mta2_coefs1[8] = { 0, 240, 460, 392, 488, 460, 460, 240 }; -static const int c2[8] = { /* mod table 2 */ +static const int mta2_coefs2[8] = { 0, 0, -208, -220, -240, -240, -220, -104 }; -static const int c3[32] = { /* shift table */ +/* shift table */ +static const int mta2_shifts[32] = { 256, 335, 438, 573, 749, 979, 1281, 1675, 2190, 2864, 3746, 4898, 6406, 8377, 10955, 14327, 18736, 24503, 32043, 41905, 54802, 71668, 93724, 122568, @@ -35,80 +33,31 @@ static const int c3[32] = { /* shift table */ }; /* expands nibble */ -static short calculate_output(int nibble, short smp1, short smp2, int mod, int sh) { +static short mta2_expand_nibble(int nibble, short hist1, short hist2, int coef_index, int shift_index) { int output; if (nibble > 7) /* sign extend */ nibble = nibble - 16; - output = (smp1 * c1[mod] + smp2 * c2[mod] + (nibble * c3[sh]) + 128) >> 8; + output = (hist1 * mta2_coefs1[coef_index] + hist2 * mta2_coefs2[coef_index] + (nibble * mta2_shifts[shift_index]) + 128) >> 8; output = clamp16(output); return (short)output; } - -/* autodetect and skip "macroblocks" */ -static void mta2_block_update(VGMSTREAMCHANNEL * stream) { - int block_type, block_size, block_tracks, repeat = 1; - - /* may need to skip N empty blocks */ - do { - block_type = read_32bitBE(stream->offset + 0x00, stream->streamfile); - block_size = read_32bitBE(stream->offset + 0x04, stream->streamfile); /* including this header */ - /* 0x08: always null */ - block_tracks = read_32bitBE(stream->offset + 0x0c, stream->streamfile); /* total tracks of variable size (can be 0) */ - - /* 0x10001: music, 0x20001: sfx?, 0xf0: loop control (goes at the end) */ - if (block_type != 0x00010001 && block_type != 0x00020001 && block_type != 0x000000F0) - return; /* not a block */ - - /* frame=010001+00/etc can be mistaken as block_type, do extra checks */ - { - int i, track_channels = 0; - uint16_t channel_layout = (block_size >> 16); - uint16_t track_size = (block_size & 0xFFFF); - - /* has chanel layout == may be a track */ - if (channel_layout > 0 && channel_layout <= 0xFF) { - for (i = 0; i < 8; i++) { - if ((channel_layout >> i) & 0x01) - track_channels++; - } - if (track_channels*0x90 == track_size) - return; - } - } - - if (block_size <= 0 || block_tracks < 0) { /* nonsense block (maybe at EOF) */ - VGM_LOG("MTA2: bad block @ 0x%x\n", (uint32_t)stream->offset); - stream->offset += 0x10; - repeat = 0; - } - else if (block_tracks == 0) { /* empty block (common), keep repeating */ - stream->offset += block_size; - } - else { /* normal block, position into next track header */ - stream->offset += 0x10; - repeat = 0; - } - } while (repeat); -} - -/* decodes a block for a channel, skipping macroblocks/tracks if needed */ -void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +/* decodes a block for a channel */ +void decode_mta2(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int samples_done = 0, sample_count = 0, channel_block_samples, channel_first_sample, frame_size = 0; int i, group, row, col; int track_channels = 0, track_channel; - /* block/track skip */ + /* track skip */ do { int num_track = 0, channel_layout; - /* autodetect and skip macroblock header */ - mta2_block_update(stream); /* parse track header (0x10) and skip tracks that our current channel doesn't belong to */ num_track = read_8bit(stream->offset+0x00,stream->streamfile); /* 0=first */ - /* 0x01(3): num_frame (0=first), 0x04(1): 0? */ + /* 0x01(3): num_frame (0=first) */ + /* 0x04(1): 0? */ channel_layout = read_8bit(stream->offset+0x05,stream->streamfile); /* bitmask, see mta2.c */ frame_size = read_16bitBE(stream->offset+0x06,stream->streamfile); /* not including this header */ /* 0x08(8): null */ @@ -151,22 +100,22 @@ void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, /* parse channel frame (header 0x04*4 + data 0x20*4) */ for (group = 0; group < 4; group++) { - short smp2, smp1, mod, sh, output; + short hist2, hist1, coefs, shift, output; int group_header = read_32bitBE(stream->offset + 0x10 + track_channel*0x90 + group*0x4, stream->streamfile); - smp2 = (short) ((group_header >> 16) & 0xfff0); /* upper 16b discarding 4b */ - smp1 = (short) ((group_header >> 4) & 0xfff0); /* lower 16b discarding 4b */ - mod = (group_header >> 5) & 0x7; /* mid 3b */ - sh = group_header & 0x1f; /* lower 5b */ + hist2 = (short) ((group_header >> 16) & 0xfff0); /* upper 16b discarding 4b */ + hist1 = (short) ((group_header >> 4) & 0xfff0); /* lower 16b discarding 4b */ + coefs = (group_header >> 5) & 0x7; /* mid 3b */ + shift = group_header & 0x1f; /* lower 5b */ /* write header samples (skips the last 2 group nibbles), like Drebin's decoder * last 2 nibbles and next 2 header hist should match though */ if (sample_count >= channel_first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = smp2; + outbuf[samples_done * channelspacing] = hist2; samples_done++; } sample_count++; if (sample_count >= channel_first_sample && samples_done < samples_to_do) { - outbuf[samples_done * channelspacing] = smp1; + outbuf[samples_done * channelspacing] = hist1; samples_done++; } sample_count++; @@ -175,7 +124,7 @@ void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, for (col = 0; col < 4*2; col++) { uint8_t nibbles = read_8bit(stream->offset + 0x10 + 0x10 + track_channel*0x90 + group*0x4 + row*0x10 + col/2, stream->streamfile); int nibble_shift = (!(col&1) ? 4 : 0); /* upper first */ - output = calculate_output((nibbles >> nibble_shift) & 0xf, smp1, smp2, mod, sh); + output = mta2_expand_nibble((nibbles >> nibble_shift) & 0xf, hist1, hist2, coefs, shift); /* ignore last 2 nibbles (uses first 2 header samples) */ if (row < 7 || col < 3*2) { @@ -186,8 +135,8 @@ void decode_mta2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, sample_count++; } - smp2 = smp1; - smp1 = output; + hist2 = hist1; + hist1 = output; } } } diff --git a/Frameworks/vgmstream/vgmstream/src/coding/mtaf_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/mtaf_decoder.c index e2d0b1825..bc07dfcf0 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/mtaf_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/mtaf_decoder.c @@ -1,15 +1,11 @@ #include "coding.h" #include "../util.h" -#define MTAF_BLOCK_SUPPORT - /* A hybrid of IMA and Yamaha ADPCM found in Metal Gear Solid 3 * Thanks to X_Tra (http://metalgear.in/) for pointing me to the step size table. * - * Layout: N tracks of 0x10 header + 0x80*2 (always 2ch; multichannels uses 4ch = 2ch track0 + 2ch track1 xN) - * "macroblocks" support is not really needed as the extractors should remove them but they are - * autodetected and skipped if found (ideally should keep a special layout/count, but this is simpler). + * Layout: N tracks of 0x10 header + 0x80*2 (always 2ch; multichannels uses 4ch = 2ch track0 + 2ch track1 xN). */ static const int index_table[16] = { @@ -84,48 +80,8 @@ static const int16_t step_size[32][16] = { -424, -1273, -2121, -2970, -3819, -4668, -5516, -6365, }, }; -#ifdef MTAF_BLOCK_SUPPORT -/* autodetect and skip "macroblocks" */ -static void mtaf_block_update(VGMSTREAMCHANNEL * stream) { - int block_type, block_size, block_empty, block_tracks, repeat = 1; - do { - block_type = read_32bitLE(stream->offset+0x00, stream->streamfile); - block_size = read_32bitLE(stream->offset+0x04, stream->streamfile); /* including this header */ - block_empty = read_32bitLE(stream->offset+0x08, stream->streamfile); /* always 0 */ - block_tracks = read_32bitLE(stream->offset+0x0c, stream->streamfile); /* total tracks of 0x110 (can be 0)*/ - - /* 0x110001: music (type 0x11=adpcm), 0xf0: loop control (goes at the end) */ - if ((block_type != 0x00110001 && block_type != 0x000000F0) || block_empty != 0) - return; /* not a block */ - - /* track=001100+01 could be mistaken as block_type, do extra checks */ - { - int track = read_8bit(stream->offset+0x10, stream->streamfile); - if (track != 0 && track != 1) - return; /* if this is a block, next header should be from track 0/1 */ - if (block_tracks > 0 && (block_size-0x10) != block_tracks*0x110) - return; /* wrong expected size */ - } - - if (block_size <= 0 || block_tracks < 0) { /* nonsense block (maybe at EOF) */ - VGM_LOG("MTAF: bad block @ %x\n", (uint32_t)stream->offset); - stream->offset += 0x10; - repeat = 0; - } - else if (block_tracks == 0) { /* empty block (common), keep repeating */ - stream->offset += block_size; - } - else { /* normal block, position into next track header */ - stream->offset += 0x10; - repeat = 0; - } - - } while(repeat); -} -#endif - -void decode_mtaf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_mtaf(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int32_t sample_count; int i; int c = channel%2; /* global channel to track channel */ @@ -133,11 +89,6 @@ void decode_mtaf(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t step_idx = stream->adpcm_step_index; - #ifdef MTAF_BLOCK_SUPPORT - /* autodetect and skip macroblock header */ - mtaf_block_update(stream); - #endif - /* read header when we hit a new track every 0x100 samples */ first_sample = first_sample % 0x100; diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ngc_dsp_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ngc_dsp_decoder.c index 6b6c96ec9..773e96125 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ngc_dsp_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ngc_dsp_decoder.c @@ -1,7 +1,7 @@ #include "coding.h" #include "../util.h" -void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i=first_sample; int32_t sample_count; @@ -37,7 +37,7 @@ void decode_ngc_dsp(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci } /* read from memory rather than a file */ -static void decode_ngc_dsp_subint_internal(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, uint8_t * mem) { +static void decode_ngc_dsp_subint_internal(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, uint8_t * mem) { int i=first_sample; int32_t sample_count; @@ -71,7 +71,7 @@ static void decode_ngc_dsp_subint_internal(VGMSTREAMCHANNEL * stream, sample * o } /* decode DSP with byte-interleaved frames (ex. 0x08: 1122112211221122) */ -void decode_ngc_dsp_subint(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int interleave) { +void decode_ngc_dsp_subint(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int interleave) { uint8_t sample_data[0x08]; int i; @@ -100,21 +100,12 @@ int32_t dsp_nibbles_to_samples(int32_t nibbles) { int32_t whole_frames = nibbles/16; int32_t remainder = nibbles%16; - /* - fprintf(stderr,"%d (%#x) nibbles => %x bytes and %d samples\n",nibbles,nibbles,whole_frames*8,remainder); - */ - -#if 0 - if (remainder > 0 && remainder < 14) - return whole_frames*14 + remainder; - else if (remainder >= 14) - fprintf(stderr,"***** last frame %d leftover nibbles makes no sense\n",remainder); -#endif if (remainder>0) return whole_frames*14+remainder-2; else return whole_frames*14; } size_t dsp_bytes_to_samples(size_t bytes, int channels) { + if (channels <= 0) return 0; return bytes / channels / 8 * 14; } diff --git a/Frameworks/vgmstream/vgmstream/src/coding/ogg_vorbis_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/ogg_vorbis_decoder.c index 6e25a9286..5ca7f47de 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/ogg_vorbis_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/ogg_vorbis_decoder.c @@ -1,55 +1,121 @@ +#include #include "coding.h" #include "../util.h" #ifdef VGM_USE_VORBIS #include -void decode_ogg_vorbis(ogg_vorbis_codec_data * data, sample * outbuf, int32_t samples_to_do, int channels) { + +static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples_to_do, float ** pcm, int disable_ordering); + + +void decode_ogg_vorbis(ogg_vorbis_codec_data * data, sample_t * outbuf, int32_t samples_to_do, int channels) { int samples_done = 0; - OggVorbis_File *ogg_vorbis_file = &data->ogg_vorbis_file; + long rc; + float **pcm_channels; /* pointer to Xiph's double array buffer */ - do { - long rc = ov_read(ogg_vorbis_file, (char *)(outbuf + samples_done*channels), - (samples_to_do - samples_done)*sizeof(sample)*channels, 0, - sizeof(sample), 1, &data->bitstream); + while (samples_done < samples_to_do) { + rc = ov_read_float( + &data->ogg_vorbis_file, /* context */ + &pcm_channels, /* buffer pointer */ + (samples_to_do - samples_done), /* samples to produce */ + &data->bitstream); /* bitstream*/ + if (rc <= 0) goto fail; /* rc is samples done */ - if (rc > 0) samples_done += rc/sizeof(sample)/channels; - else return; - } while (samples_done < samples_to_do); + pcm_convert_float_to_16(channels, outbuf, rc, pcm_channels, data->disable_reordering); - swap_samples_le(outbuf, samples_to_do*channels); + outbuf += rc * channels; + samples_done += rc; + + +#if 0 // alt decoding + /* we use ov_read_float as to reuse the xiph's buffer for easier remapping, + * but seems ov_read is slightly faster due to optimized (asm) float-to-int. */ + rc = ov_read( + &data->ogg_vorbis_file, /* context */ + (char *)(outbuf), /* buffer */ + (samples_to_do - samples_done) * sizeof(sample_t) * channels, /* length in bytes */ + 0, /* pcm endianness */ + sizeof(sample), /* pcm size */ + 1, /* pcm signedness */ + &data->bitstream); /* bitstream */ + if (rc <= 0) goto fail; /* rc is bytes done (for all channels) */ + + swap_samples_le(outbuf, rc / sizeof(sample_t)); /* endianness is a bit weird with ov_read though */ + + outbuf += rc / sizeof(sample_t); + samples_done += rc / sizeof(sample_t) / channels; +#endif + } + + return; +fail: + VGM_LOG("OGG: error %lx during decode\n", rc); + memset(outbuf, 0, (samples_to_do - samples_done) * channels * sizeof(sample)); } +/* vorbis encodes channels in non-standard order, so we remap during conversion to fix this oddity. + * (feels a bit weird as one would think you could leave as-is and set the player's output + * order, but that isn't possible and remapping is what FFmpeg and every other plugin do). */ +static const int xiph_channel_map[8][8] = { + { 0 }, /* 1ch: FC > same */ + { 0, 1 }, /* 2ch: FL FR > same */ + { 0, 2, 1 }, /* 3ch: FL FC FR > FL FR FC */ + { 0, 1, 2, 3 }, /* 4ch: FL FR BL BR > same */ + { 0, 2, 1, 3, 4 }, /* 5ch: FL FC FR BL BR > FL FR FC BL BR */ + { 0, 2, 1, 5, 3, 4 }, /* 6ch: FL FC FR BL BR LFE > FL FR FC LFE BL BR */ + { 0, 2, 1, 6, 5, 3, 4 }, /* 7ch: FL FC FR SL SR BC LFE > FL FR FC LFE BC SL SR */ + { 0, 2, 1, 7, 5, 6, 3, 4 }, /* 8ch: FL FC FR SL SR BL BR LFE > FL FR FC LFE BL BR SL SR */ +}; -void reset_ogg_vorbis(VGMSTREAM *vgmstream) { - OggVorbis_File *ogg_vorbis_file; - ogg_vorbis_codec_data *data = vgmstream->codec_data; - if (!data) return; +/* converts from internal Vorbis format to standard PCM and remaps (mostly from Xiph's decoder_example.c) */ +static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples_to_do, float ** pcm, int disable_ordering) { + int ch, s, ch_map; + sample_t *ptr; + float *channel; - ogg_vorbis_file = &(data->ogg_vorbis_file); + /* convert float PCM (multichannel float array, with pcm[0]=ch0, pcm[1]=ch1, pcm[2]=ch0, etc) + * to 16 bit signed PCM ints (host order) and interleave + fix clipping */ + for (ch = 0; ch < channels; ch++) { + ch_map = disable_ordering ? + ch : + (channels > 8) ? ch : xiph_channel_map[channels - 1][ch]; /* put Vorbis' ch to other outbuf's ch */ + ptr = outbuf + ch; + channel = pcm[ch_map]; + for (s = 0; s < samples_to_do; s++) { + int val = (int)floor(channel[s] * 32767.0f + 0.5f); /* use floorf? doesn't seem any faster */ + if (val > 32767) val = 32767; + else if (val < -32768) val = -32768; - ov_pcm_seek(ogg_vorbis_file, 0); -} - -void seek_ogg_vorbis(VGMSTREAM *vgmstream, int32_t num_sample) { - OggVorbis_File *ogg_vorbis_file; - ogg_vorbis_codec_data *data = (ogg_vorbis_codec_data *)(vgmstream->codec_data); - if (!data) return; - - ogg_vorbis_file = &(data->ogg_vorbis_file); - - ov_pcm_seek_lap(ogg_vorbis_file, num_sample); -} - -void free_ogg_vorbis(ogg_vorbis_codec_data *data) { - if (data) { - OggVorbis_File *ogg_vorbis_file = &(data->ogg_vorbis_file); - - ov_clear(ogg_vorbis_file); - - close_streamfile(data->ov_streamfile.streamfile); - free(data); + *ptr = val; + ptr += channels; + } } } +/* ********************************************** */ + +void reset_ogg_vorbis(VGMSTREAM *vgmstream) { + ogg_vorbis_codec_data *data = vgmstream->codec_data; + if (!data) return; + + ov_pcm_seek(&data->ogg_vorbis_file, 0); +} + +void seek_ogg_vorbis(VGMSTREAM *vgmstream, int32_t num_sample) { + ogg_vorbis_codec_data *data = vgmstream->codec_data; + if (!data) return; + + ov_pcm_seek_lap(&data->ogg_vorbis_file, num_sample); +} + +void free_ogg_vorbis(ogg_vorbis_codec_data *data) { + if (!data) return; + + ov_clear(&data->ogg_vorbis_file); + + close_streamfile(data->ov_streamfile.streamfile); + free(data); +} + #endif diff --git a/Frameworks/vgmstream/vgmstream/src/coding/oki_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/oki_decoder.c index 9f39566aa..1fc07b346 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/oki_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/oki_decoder.c @@ -87,7 +87,7 @@ static void oki16_expand_nibble(VGMSTREAMCHANNEL * stream, off_t byte_offset, in * so it's needs GENH/TXTH. Sample rate can only be base_value divided by 1/2/3/4, where * base_value is approximately ~31468.5 (follows hardware clocks), mono or interleaved for stereo. */ -void decode_pcfx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int mode) { +void decode_pcfx(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int mode) { int i, sample_count = 0; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -108,7 +108,7 @@ void decode_pcfx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, /* OKI variation with 16-bit output (vs standard's 12-bit), found in FrontWing's PS2 games (Sweet Legacy, Hooligan). * Reverse engineered from the ELF with help from the folks at hcs. */ -void decode_oki16(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_oki16(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, sample_count = 0; int32_t hist1 = stream->adpcm_history1_32; int step_index = stream->adpcm_step_index; @@ -140,6 +140,7 @@ void decode_oki16(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing size_t oki_bytes_to_samples(size_t bytes, int channels) { + if (channels <= 0) return 0; /* 2 samples per byte (2 nibbles) in stereo or mono config */ return bytes * 2 / channels; } diff --git a/Frameworks/vgmstream/vgmstream/src/coding/pcm_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/pcm_decoder.c index 93cc1a6ec..cd1b623e5 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/pcm_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/pcm_decoder.c @@ -2,7 +2,7 @@ #include "../util.h" #include -void decode_pcm16le(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm16le(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -11,7 +11,7 @@ void decode_pcm16le(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci } } -void decode_pcm16be(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm16be(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -20,7 +20,7 @@ void decode_pcm16be(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci } } -void decode_pcm16_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian) { +void decode_pcm16_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int big_endian) { int i, sample_count; int16_t (*read_16bit)(off_t,STREAMFILE*) = big_endian ? read_16bitBE : read_16bitLE; @@ -29,7 +29,7 @@ void decode_pcm16_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspa } } -void decode_pcm8(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm8(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -38,7 +38,7 @@ void decode_pcm8(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, } } -void decode_pcm8_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm8_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -47,7 +47,7 @@ void decode_pcm8_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspac } } -void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -57,7 +57,7 @@ void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample * outbuf, int channe } } -void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -67,7 +67,7 @@ void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int ch } } -void decode_pcm8_sb(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_pcm8_sb(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i; int32_t sample_count; @@ -78,7 +78,7 @@ void decode_pcm8_sb(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspaci } } -void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, nibble_shift, is_high_first, is_stereo; int32_t sample_count; int16_t v; @@ -101,7 +101,7 @@ void decode_pcm4(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outb } } -void decode_pcm4_unsigned(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +void decode_pcm4_unsigned(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, nibble_shift, is_high_first, is_stereo; int32_t sample_count; int16_t v; @@ -125,7 +125,7 @@ void decode_pcm4_unsigned(VGMSTREAM * vgmstream, VGMSTREAMCHANNEL * stream, samp } static int expand_ulaw(uint8_t ulawbyte) { - int sign, segment, quantization, new_sample; + int sign, segment, quantization, sample; const int bias = 0x84; ulawbyte = ~ulawbyte; /* stored in complement */ @@ -133,9 +133,9 @@ static int expand_ulaw(uint8_t ulawbyte) { segment = (ulawbyte & 0x70) >> 4; /* exponent */ quantization = ulawbyte & 0x0F; /* mantissa */ - new_sample = (quantization << 3) + bias; /* add bias */ - new_sample <<= segment; - new_sample = (sign) ? (bias - new_sample) : (new_sample - bias); /* remove bias */ + sample = (quantization << 3) + bias; /* add bias */ + sample <<= segment; + sample = (sign) ? (bias - sample) : (sample - bias); /* remove bias */ #if 0 // the above follows Sun's implementation, but this works too { @@ -145,11 +145,11 @@ static int expand_ulaw(uint8_t ulawbyte) { } #endif - return new_sample; + return sample; } /* decodes u-law (ITU G.711 non-linear PCM), from g711.c */ -void decode_ulaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_ulaw(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count; for (i=first_sample,sample_count=0; i> 4; /* exponent */ quantization = alawbyte & 0x0F; /* mantissa */ - new_sample = (quantization << 4); + sample = (quantization << 4); switch (segment) { case 0: - new_sample += 8; + sample += 8; break; case 1: - new_sample += 0x108; + sample += 0x108; break; default: - new_sample += 0x108; - new_sample <<= segment - 1; + sample += 0x108; + sample <<= segment - 1; break; } - new_sample = (sign) ? new_sample : -new_sample; + sample = (sign) ? sample : -sample; - return new_sample; + return sample; } /* decodes a-law (ITU G.711 non-linear PCM), from g711.c */ -void decode_alaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_alaw(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count; for (i=first_sample,sample_count=0; istreamfile); - new_sample = i&1 ? /* low nibble first */ + sample = i&1 ? /* low nibble first */ (nibbles >> 4) & 0x0f : (nibbles >> 0) & 0x0f; - new_sample = (int16_t)((new_sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */ - new_sample = (int)(new_sample + ps_adpcm_coefs_f[coef_index][0]*hist1 + ps_adpcm_coefs_f[coef_index][1]*hist2); - new_sample = clamp16(new_sample); + sample = (int16_t)((sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */ + sample = (int)(sample + ps_adpcm_coefs_f[coef_index][0]*hist1 + ps_adpcm_coefs_f[coef_index][1]*hist2); + sample = clamp16(sample); } - outbuf[sample_count] = new_sample; + outbuf[sample_count] = sample; sample_count += channelspacing; hist2 = hist1; - hist1 = new_sample; + hist1 = sample; } stream->adpcm_history1_32 = hist1; @@ -104,7 +104,7 @@ void decode_psx(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, * Found in some PC/PS3 games (FF XI in sizes 3/5/9/41, Afrika in size 4, Blur/James Bond in size 33, etc). * * Uses int math to decode, which seems more likely (based on FF XI PC's code in Moogle Toolbox). */ -void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size) { +void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int frame_size) { off_t frame_offset; int i, frames_in, sample_count = 0; size_t bytes_per_frame, samples_per_frame; @@ -131,21 +131,21 @@ void decode_psx_configurable(VGMSTREAMCHANNEL * stream, sample * outbuf, int cha /* decode nibbles */ for (i = first_sample; i < first_sample + samples_to_do; i++) { - int32_t new_sample = 0; + int32_t sample = 0; uint8_t nibbles = (uint8_t)read_8bit(frame_offset+0x01+i/2,stream->streamfile); - new_sample = i&1 ? /* low nibble first */ + sample = i&1 ? /* low nibble first */ (nibbles >> 4) & 0x0f : (nibbles >> 0) & 0x0f; - new_sample = (int16_t)((new_sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */ - new_sample = new_sample + ((ps_adpcm_coefs_i[coef_index][0]*hist1 + ps_adpcm_coefs_i[coef_index][1]*hist2) >> 6); - new_sample = clamp16(new_sample); + sample = (int16_t)((sample << 12) & 0xf000) >> shift_factor; /* 16b sign extend + scale */ + sample = sample + ((ps_adpcm_coefs_i[coef_index][0]*hist1 + ps_adpcm_coefs_i[coef_index][1]*hist2) >> 6); + sample = clamp16(sample); - outbuf[sample_count] = new_sample; + outbuf[sample_count] = sample; sample_count += channelspacing; hist2 = hist1; - hist1 = new_sample; + hist1 = sample; } stream->adpcm_history1_32 = hist1; @@ -176,6 +176,9 @@ static int ps_find_loop_offsets_internal(STREAMFILE *streamFile, off_t start_off int detect_full_loops = config & 1; + if (data_size == 0 || channels == 0 || (channels > 0 && interleave == 0)) + return 0; + while (offset < max_offset) { uint8_t flag = (uint8_t)read_8bit(offset+0x01,streamFile) & 0x0F; /* lower nibble only (for HEVAG) */ @@ -268,7 +271,70 @@ int ps_find_loop_offsets_full(STREAMFILE *streamFile, off_t start_offset, size_t return ps_find_loop_offsets_internal(streamFile, start_offset, data_size, channels, interleave, out_loop_start, out_loop_end, 1); } +size_t ps_find_padding(STREAMFILE *streamFile, off_t start_offset, size_t data_size, int channels, size_t interleave, int discard_empty) { + off_t min_offset, offset; + size_t frame_size = 0x10; + size_t padding_size = 0; + size_t interleave_consumed = 0; + + + if (data_size == 0 || channels == 0 || (channels > 0 && interleave == 0)) + return 0; + + offset = start_offset + data_size; + + /* in rare cases (ex. Gitaroo Man) channels have inconsistent empty padding, use first as guide */ + offset = offset - interleave * (channels - 1); + + /* some files have padding spanning multiple interleave blocks */ + min_offset = start_offset; //offset - interleave; + + while (offset > min_offset) { + uint32_t f1,f2,f3,f4; + uint8_t flag; + int is_empty = 0; + + offset -= frame_size; + + f1 = read_32bitBE(offset+0x00,streamFile); + f2 = read_32bitBE(offset+0x04,streamFile); + f3 = read_32bitBE(offset+0x08,streamFile); + f4 = read_32bitBE(offset+0x0c,streamFile); + flag = (f1 >> 16) & 0xFF; + + if (f1 == 0 && f2 == 0 && f3 == 0 && f4 == 0) + is_empty = 1; + + if (!is_empty && discard_empty) { + if (flag == 0x07 || flag == 0x77) + is_empty = 1; /* 'discard frame' flag */ + else if ((f1 & 0xFF00FFFF) == 0 && f2 == 0 && f3 == 0 && f4 == 0) + is_empty = 1; /* silent with flags (typical for looping files) */ + else if ((f1 & 0xFF00FFFF) == 0x0C000000 && f2 == 0 && f3 == 0 && f4 == 0) + is_empty = 1; /* silent (maybe shouldn't ignore flag 0x03?) */ + else if ((f1 & 0x0000FFFF) == 0x00007777 && f2 == 0x77777777 && f3 ==0x77777777 && f4 == 0x77777777) + is_empty = 1; /* silent-ish */ + } + + if (!is_empty) + break; + + padding_size += frame_size * channels; + + /* skip other channels */ + interleave_consumed += 0x10; + if (interleave_consumed == interleave) { + interleave_consumed = 0; + offset -= interleave*(channels - 1); + } + } + + return padding_size; +} + + size_t ps_bytes_to_samples(size_t bytes, int channels) { + if (channels <= 0) return 0; return bytes / channels / 0x10 * 28; } diff --git a/Frameworks/vgmstream/vgmstream/src/coding/vorbis_custom_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/vorbis_custom_decoder.c index b5f3da953..f0c2d2879 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/vorbis_custom_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/vorbis_custom_decoder.c @@ -7,7 +7,7 @@ #define VORBIS_DEFAULT_BUFFER_SIZE 0x8000 /* should be at least the size of the setup header, ~0x2000 */ -static void pcm_convert_float_to_16(vorbis_custom_codec_data * data, sample * outbuf, int samples_to_do, float ** pcm); +static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples_to_do, float ** pcm); /** * Inits a vorbis stream of some custom variety. @@ -75,7 +75,7 @@ fail: } /* Decodes Vorbis packets into a libvorbis sample buffer, and copies them to outbuf */ -void decode_vorbis_custom(VGMSTREAM * vgmstream, sample * outbuf, int32_t samples_to_do, int channels) { +void decode_vorbis_custom(VGMSTREAM * vgmstream, sample_t * outbuf, int32_t samples_to_do, int channels) { VGMSTREAMCHANNEL *stream = &vgmstream->ch[0]; vorbis_custom_codec_data * data = vgmstream->codec_data; size_t stream_size = get_streamfile_size(stream->streamfile); @@ -112,7 +112,7 @@ void decode_vorbis_custom(VGMSTREAM * vgmstream, sample * outbuf, int32_t sample /* get max samples and convert from Vorbis float pcm to 16bit pcm */ if (samples_to_get > samples_to_do - samples_done) samples_to_get = samples_to_do - samples_done; - pcm_convert_float_to_16(data, outbuf + samples_done * channels, samples_to_get, pcm); + pcm_convert_float_to_16(data->vi.channels, outbuf + samples_done * channels, samples_to_get, pcm); samples_done += samples_to_get; } @@ -167,21 +167,24 @@ decode_fail: } /* converts from internal Vorbis format to standard PCM (mostly from Xiph's decoder_example.c) */ -static void pcm_convert_float_to_16(vorbis_custom_codec_data * data, sample * outbuf, int samples_to_do, float ** pcm) { - int i,j; +static void pcm_convert_float_to_16(int channels, sample_t * outbuf, int samples_to_do, float ** pcm) { + int ch, s; + sample_t *ptr; + float *channel; /* convert float PCM (multichannel float array, with pcm[0]=ch0, pcm[1]=ch1, pcm[2]=ch0, etc) * to 16 bit signed PCM ints (host order) and interleave + fix clipping */ - for (i = 0; i < data->vi.channels; i++) { - sample *ptr = outbuf + i; - float *mono = pcm[i]; - for (j = 0; j < samples_to_do; j++) { - int val = (int)floor(mono[j] * 32767.f + .5f); + for (ch = 0; ch < channels; ch++) { + /* channels should be in standard order unlike Ogg Vorbis (at least in FSB) */ + ptr = outbuf + ch; + channel = pcm[ch]; + for (s = 0; s < samples_to_do; s++) { + int val = (int)floor(channel[s] * 32767.0f + 0.5f); if (val > 32767) val = 32767; - if (val < -32768) val = -32768; + else if (val < -32768) val = -32768; *ptr = val; - ptr += data->vi.channels; + ptr += channels; } } } diff --git a/Frameworks/vgmstream/vgmstream/src/coding/yamaha_decoder.c b/Frameworks/vgmstream/vgmstream/src/coding/yamaha_decoder.c index 15b1e9af3..2f4261dc4 100644 --- a/Frameworks/vgmstream/vgmstream/src/coding/yamaha_decoder.c +++ b/Frameworks/vgmstream/vgmstream/src/coding/yamaha_decoder.c @@ -8,6 +8,11 @@ static const unsigned int scale_step[16] = { 230, 230, 230, 230, 307, 409, 512, 614 }; +/* actually implemented with if-else/switchs but that's too goofy */ +static const int scale_step_aska[8] = { + 57, 57, 57, 57, 77, 102, 128, 153, +}; + /* expand an unsigned four bit delta to a wider signed range */ static const int scale_delta[16] = { 1, 3, 5, 7, 9, 11, 13, 15, @@ -15,8 +20,9 @@ static const int scale_delta[16] = { }; -/* raw Yamaha ADPCM a.k.a AICA as it's mainly used in Naomi/Dreamcast (also in RIFF and older arcade sound chips). */ -void decode_aica(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo) { +/* raw Yamaha ADPCM a.k.a AICA as it's prominently used in Naomi/Dreamcast's Yamaha AICA sound chip, + * also found in Windows RIFF and older Yamaha's arcade sound chips. */ +void decode_yamaha(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel, int is_stereo) { int i, sample_count; int32_t hist1 = stream->adpcm_history1_16; @@ -52,9 +58,8 @@ void decode_aica(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, stream->adpcm_step_index = step_size; } -/* Yamaha ADPCM, in headered frames like MS-IMA. Possibly originated from Yamaha's SMAF tools - * (Windows ACM encoder/decoder was given in their site). Some info from Rockbox's yamaha_adpcm.c */ -void decode_yamaha(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { +/* tri-Ace Aska ADPCM, same-ish with modified step table (reversed from Android SO's .so) */ +void decode_aska(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) { int i, sample_count, num_frame; int32_t hist1 = stream->adpcm_history1_32; int step_size = stream->adpcm_step_index; @@ -84,15 +89,15 @@ void decode_yamaha(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacin (!(channel&1) ? 0:4) : (!(i&1) ? 0:4); /* even = low, odd = high */ - /* Yamaha/AICA expand, but same result as IMA's (((delta * 2 + 1) * step) >> 3) */ - sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift)&0xf; - sample_delta = (step_size * scale_delta[sample_nibble]) / 8; + sample_nibble = (read_8bit(byte_offset,stream->streamfile) >> nibble_shift) & 0xf; + sample_delta = ((((sample_nibble & 0x7) * 2) | 1) * step_size) >> 3; /* like 'mul' IMA with 'or' */ + if (sample_nibble & 8) sample_delta = -sample_delta; sample_decoded = hist1 + sample_delta; - outbuf[sample_count] = clamp16(sample_decoded); + outbuf[sample_count] = sample_decoded; /* not clamped */ hist1 = outbuf[sample_count]; - step_size = (step_size * scale_step[sample_nibble]) >> 8; + step_size = (step_size * scale_step_aska[sample_nibble & 0x07]) >> 6; if (step_size < 0x7f) step_size = 0x7f; if (step_size > 0x6000) step_size = 0x6000; } @@ -102,7 +107,7 @@ void decode_yamaha(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacin } /* Yamaha ADPCM with unknown expand variation (noisy), step size is double of normal Yamaha? */ -void decode_yamaha_nxap(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { +void decode_nxap(VGMSTREAMCHANNEL * stream, sample_t * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) { int i, sample_count, num_frame; int32_t hist1 = stream->adpcm_history1_32; int step_size = stream->adpcm_step_index; @@ -145,14 +150,15 @@ void decode_yamaha_nxap(VGMSTREAMCHANNEL * stream, sample * outbuf, int channels stream->adpcm_step_index = step_size; } -size_t aica_bytes_to_samples(size_t bytes, int channels) { +size_t yamaha_bytes_to_samples(size_t bytes, int channels) { + if (channels <= 0) return 0; /* 2 samples per byte (2 nibbles) in stereo or mono config */ return bytes * 2 / channels; } -size_t yamaha_bytes_to_samples(size_t bytes, int channels) { +size_t aska_bytes_to_samples(size_t bytes, int channels) { int block_align = 0x40; - + if (channels <= 0) return 0; return (bytes / block_align) * (block_align - 0x04*channels) * 2 / channels + ((bytes % block_align) ? ((bytes % block_align) - 0x04*channels) * 2 / channels : 0); } diff --git a/Frameworks/vgmstream/vgmstream/src/formats.c b/Frameworks/vgmstream/vgmstream/src/formats.c index 03903b8e2..6aec98765 100644 --- a/Frameworks/vgmstream/vgmstream/src/formats.c +++ b/Frameworks/vgmstream/vgmstream/src/formats.c @@ -18,6 +18,7 @@ static const char* extension_list[] = { "2dx9", "2pfs", "800", + "9tav", //"aac", //common "aa3", //FFmpeg/not parsed (ATRAC3/ATRAC3PLUS/MP3/LPCM/WMA) @@ -42,6 +43,7 @@ static const char* extension_list[] = { "ahv", "ai", //"aif", //common + "aif-Loop", "aifc", //common? "aifcl", //fake extension for .aif??? //"aiff", //common @@ -71,6 +73,7 @@ static const char* extension_list[] = { "b1s", "baf", "baka", + "bank", "bar", "bcstm", "bcwav", @@ -90,6 +93,7 @@ static const char* extension_list[] = { "bmdx", "bms", "bnk", + "bnm", "bns", "bnsf", "bo2", @@ -123,6 +127,7 @@ static const char* extension_list[] = { "de2", "dec", "dmsg", + "ds2", //txth/reserved [Star Wars Bounty Hunter (GC)] "dsf", "dsp", "dspw", @@ -183,6 +188,7 @@ static const char* extension_list[] = { "idx", "ikm", "ild", + "ilv", //txth/reserved [Star Wars Episode III (PS2)] "imc", "int", "isd", @@ -208,6 +214,9 @@ static const char* extension_list[] = { "l", "laac", //fake extension for .aac (tri-Ace) + "laif", //fake extension for .aif (various) + "laiff", //fake extension for .aiff + "laifc", //fake extension for .aifc "lac3", //fake extension for .ac3, FFmpeg/not parsed "lasf", //fake extension for .asf (various) "leg", @@ -366,7 +375,6 @@ static const char* extension_list[] = { "sbin", "sc", "scd", - "sck", "sd9", "sdf", "sdt", @@ -421,11 +429,13 @@ static const char* extension_list[] = { "sx", "sxd", "sxd2", + "sxd3", "tec", "tgq", "thp", "tk5", + "tmx", "tra", "tun", "txth", @@ -456,7 +466,9 @@ static const char* extension_list[] = { "vig", "vis", "vms", + "vmu", //txth/reserved [Red Faction (PS2)] "voi", + "vp6", "vpk", "vs", "vsf", @@ -498,6 +510,7 @@ static const char* extension_list[] = { "xa30", "xag", "xau", + "xen", "xma", "xma2", "xmu", @@ -608,6 +621,7 @@ static const coding_info coding_info_list[] = { {coding_WV6_IMA, "Gorilla Systems WV6 4-bit IMA ADPCM"}, {coding_ALP_IMA, "High Voltage ALP 4-bit IMA ADPCM"}, {coding_FFTA2_IMA, "Final Fantasy Tactics A2 4-bit IMA ADPCM"}, + {coding_BLITZ_IMA, "Blitz Games 4-bit IMA ADPCM"}, {coding_MS_IMA, "Microsoft 4-bit IMA ADPCM"}, {coding_XBOX_IMA, "XBOX 4-bit IMA ADPCM"}, @@ -630,10 +644,10 @@ static const coding_info coding_info_list[] = { {coding_MSADPCM_int, "Microsoft 4-bit ADPCM (mono/interleave)"}, {coding_MSADPCM_ck, "Microsoft 4-bit ADPCM (Cricket Audio)"}, {coding_WS, "Westwood Studios VBR ADPCM"}, - {coding_AICA, "Yamaha 4-bit ADPCM"}, - {coding_AICA_int, "Yamaha 4-bit ADPCM (mono/interleave)"}, - {coding_YAMAHA, "Yamaha 4-bit ADPCM (framed)"}, - {coding_YAMAHA_NXAP, "Yamaha NXAP 4-bit ADPCM"}, + {coding_YAMAHA, "Yamaha 4-bit ADPCM"}, + {coding_YAMAHA_int, "Yamaha 4-bit ADPCM (mono/interleave)"}, + {coding_ASKA, "tri-Ace Aska 4-bit ADPCM"}, + {coding_NXAP, "Nex NXAP 4-bit ADPCM"}, {coding_NDS_PROCYON, "Procyon Studio Digital Sound Elements NDS 4-bit APDCM"}, {coding_L5_555, "Level-5 0x555 4-bit ADPCM"}, {coding_LSF, "lsf 4-bit ADPCM"}, @@ -698,7 +712,6 @@ static const layout_info layout_info_list[] = { {layout_segmented, "segmented"}, {layout_layered, "layered"}, - {layout_aix, "AIX"}, {layout_blocked_mxch, "blocked (MxCh)"}, {layout_blocked_ast, "blocked (AST)"}, @@ -762,7 +775,7 @@ static const meta_info meta_info_list[] = { {meta_DSP_CSTR, "Namco Cstr header"}, {meta_GCSW, "GCSW header"}, {meta_PS2_SShd, "Sony ADS header"}, - {meta_PS2_NPSF, "Namco Production Sound File (NPSF) header"}, + {meta_NPS, "Namco NPSF header"}, {meta_RWSD, "Nintendo RWSD header (single stream)"}, {meta_RWAR, "Nintendo RWAR header (single RWAV stream)"}, {meta_RWAV, "Nintendo RWAV header"}, @@ -838,7 +851,7 @@ static const meta_info meta_info_list[] = { {meta_MUSX_V201, "MUSX / Version 201 Header"}, {meta_LEG, "Legaia 2 - Duel Saga LEG Header"}, {meta_FILP, "Bio Hazard - Gun Survivor FILp Header"}, - {meta_IKM, "Zwei!! IKM Header"}, + {meta_IKM, "MiCROViSiON IKM header"}, {meta_SFS, "Baroque SFS Header"}, {meta_SAT_DVI, "Konami KCEN DVI. header"}, {meta_DC_KCEY, "Konami KCEY KCEYCOMP header"}, @@ -901,8 +914,8 @@ static const meta_info meta_info_list[] = { {meta_RSD6WMA, "Radical RSD6/WMA header"}, {meta_DC_ASD, "ASD Header"}, {meta_NAOMI_SPSD, "Naomi SPSD header"}, - {meta_FFXI_BGW, "BGW BGMStream header"}, - {meta_FFXI_SPW, "SPW SeWave header"}, + {meta_FFXI_BGW, "Square Enix .BGW header"}, + {meta_FFXI_SPW, "Square Enix .SPW header"}, {meta_PS2_ASS, "SystemSoft .ASS header"}, {meta_NUB_IDSP, "Namco NUB IDSP header"}, {meta_IDSP_NL, "Next Level IDSP header"}, @@ -921,7 +934,7 @@ static const meta_info meta_info_list[] = { {meta_NGC_SSM, "SSM DSP Header"}, {meta_PS2_JOE, "Asobo Studio .JOE header"}, {meta_VGS, "Guitar Hero VGS Header"}, - {meta_DC_DCSW_DCS, "Evil Twin DCS file with helper"}, + {meta_DCS_WAV, "In Utero DCS+WAV header"}, {meta_SMP, "Infernal Engine .smp header"}, {meta_MUL, "Crystal Dynamics .MUL header"}, {meta_THP, "THP Movie File Format Header"}, @@ -995,7 +1008,7 @@ static const meta_info meta_info_list[] = { {meta_PS2_B1S, "B1S header"}, {meta_PS2_WAD, "WAD header"}, {meta_DSP_XIII, "XIII dsp header"}, - {meta_DSP_CABELAS, "Cabelas games dsp header"}, + {meta_DSP_CABELAS, "Cabelas games .DSP header"}, {meta_PS2_ADM, "Dragon Quest V .ADM raw header"}, {meta_PS2_LPCM, "LPCM header"}, {meta_PS2_VMS, "VMS Header"}, @@ -1009,7 +1022,7 @@ static const meta_info meta_info_list[] = { {meta_SQEX_SCD, "Square-Enix SCD header"}, {meta_NGC_NST_DSP, "Animaniacs NST header"}, {meta_BAF, "Bizarre Creations .baf header"}, - {meta_PS3_MSF, "Sony MSF header"}, + {meta_MSF, "Sony MSF header"}, {meta_NUB_VAG, "Namco NUB VAG header"}, {meta_PS3_PAST, "SNDP header"}, {meta_SGXD, "Sony SGXD header"}, @@ -1030,7 +1043,7 @@ static const meta_info meta_info_list[] = { {meta_OTNS_ADP, "Omikron: The Nomad Soul ADP header"}, {meta_EB_SFX, "Excitebots .sfx header"}, {meta_EB_SF0, "assumed Excitebots .sf0 by extension"}, - {meta_PS2_MTAF, "Konami MTAF header"}, + {meta_MTAF, "Konami MTAF header"}, {meta_PS2_VAG1, "Konami VAG1 header"}, {meta_PS2_VAG2, "Konami VAG2 header"}, {meta_TUN, "Lego Racers ALP header"}, @@ -1071,7 +1084,7 @@ static const meta_info meta_info_list[] = { {meta_TA_AAC_X360, "tri-Ace AAC (X360) header"}, {meta_TA_AAC_PS3, "tri-Ace AAC (PS3) header"}, {meta_TA_AAC_MOBILE, "tri-Ace AAC (Mobile) header"}, - {meta_PS3_MTA2, "Konami MTA2 header"}, + {meta_MTA2, "Konami MTA2 header"}, {meta_NGC_ULW, "Criterion ULW raw header"}, {meta_PC_XA30, "Reflections XA30 PC header"}, {meta_WII_04SW, "Reflections 04SW header"}, @@ -1170,6 +1183,12 @@ static const meta_info meta_info_list[] = { {meta_GIN, "Electronic Arts Gnsu header"}, {meta_DSF, "Ocean DSF header"}, {meta_208, "Ocean .208 header"}, + {meta_DSP_DS2, "LucasArts .DS2 header"}, + {meta_MUS_VC, "Vicious Cycle .MUS header"}, + {meta_STRM_ABYLIGHT, "Abylight STRM header"}, + {meta_MSF_KONAMI, "Konami MSF header"}, + {meta_XWMA_KONAMI, "Konami XWMA header"}, + {meta_9TAV, "Konami 9TAV header"}, }; diff --git a/Frameworks/vgmstream/vgmstream/src/layout/aix_layout.c b/Frameworks/vgmstream/vgmstream/src/layout/aix_layout.c deleted file mode 100644 index 2e8a7b831..000000000 --- a/Frameworks/vgmstream/vgmstream/src/layout/aix_layout.c +++ /dev/null @@ -1,92 +0,0 @@ -#include "layout.h" -#include "../vgmstream.h" -#include "../coding/coding.h" - -void render_vgmstream_aix(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { - int samples_written=0; - aix_codec_data *data = vgmstream->codec_data; - - while (samples_writtensample_counts[data->current_segment]; - int current_stream; - int channels_sofar = 0; - - if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) { - data->current_segment = 1; - for (current_stream = 0; current_stream < data->stream_count; current_stream++) - { - int i; - reset_vgmstream(data->adxs[data->current_segment*data->stream_count+current_stream]); - - /* carry over the history from the loop point */ - for (i=0;iadxs[data->stream_count+current_stream]->channels;i++) - { - data->adxs[1*data->stream_count+current_stream]->ch[i].adpcm_history1_32 = - data->adxs[0+current_stream]->ch[i].adpcm_history1_32; - data->adxs[1*data->stream_count+current_stream]->ch[i].adpcm_history2_32 = - data->adxs[0+current_stream]->ch[i].adpcm_history2_32; - } - } - vgmstream->samples_into_block = 0; - continue; - } - - samples_to_do = vgmstream_samples_to_do(samples_this_block, 1, vgmstream); - - /*printf("samples_to_do=%d,samples_this_block=%d,samples_written=%d,sample_count=%d\n",samples_to_do,samples_this_block,samples_written,sample_count);*/ - - if (samples_written+samples_to_do > sample_count) - samples_to_do=sample_count-samples_written; - - if (samples_to_do == 0) - { - int i; - data->current_segment++; - /*printf("next %d, %d samples\n",data->current_file,data->files[data->current_file]->total_values/data->files[data->current_file]->info.channels);*/ - for (current_stream = 0; current_stream < data->stream_count; current_stream++) - { - reset_vgmstream(data->adxs[data->current_segment*data->stream_count+current_stream]); - - /* carry over the history from the previous segment */ - for (i=0;iadxs[data->current_segment*data->stream_count+current_stream]->channels;i++) - { - data->adxs[data->current_segment*data->stream_count+current_stream]->ch[i].adpcm_history1_32 = - data->adxs[(data->current_segment-1)*data->stream_count+current_stream]->ch[i].adpcm_history1_32; - data->adxs[data->current_segment*data->stream_count+current_stream]->ch[i].adpcm_history2_32 = - data->adxs[(data->current_segment-1)*data->stream_count+current_stream]->ch[i].adpcm_history2_32; - } - } - vgmstream->samples_into_block = 0; - continue; - } - - /*printf("decode %d samples file %d\n",samples_to_do,data->current_file);*/ - if (samples_to_do > AIX_BUFFER_SIZE/2) - { - samples_to_do = AIX_BUFFER_SIZE/2; - } - - for (current_stream = 0; current_stream < data->stream_count; current_stream++) - { - int i,j; - VGMSTREAM *adx = data->adxs[data->current_segment*data->stream_count+current_stream]; - - render_vgmstream(data->buffer,samples_to_do,adx); - - for (i = 0; i < samples_to_do; i++) - { - for (j = 0; j < adx->channels; j++) - { - buffer[(i+samples_written)*vgmstream->channels+channels_sofar+j] = data->buffer[i*adx->channels+j]; - } - } - - channels_sofar += adx->channels; - } - - samples_written += samples_to_do; - vgmstream->current_sample += samples_to_do; - vgmstream->samples_into_block+=samples_to_do; - } -} diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked.c index c6eea228d..4bfe92725 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/blocked.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked.c @@ -5,7 +5,7 @@ /* Decodes samples for blocked streams. * Data is divided into headered blocks with a bunch of data. The layout calls external helper functions * when a block is decoded, and those must parse the new block and move offsets accordingly. */ -void render_vgmstream_blocked(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { +void render_vgmstream_blocked(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { int samples_written = 0; int frame_size, samples_per_frame, samples_this_block; @@ -41,14 +41,14 @@ void render_vgmstream_blocked(sample * buffer, int32_t sample_count, VGMSTREAM * if (samples_this_block < 0) { /* probably block bug or EOF, next calcs would give wrong values/segfaults/infinite loop */ VGM_LOG("layout_blocked: wrong block samples at 0x%x\n", (uint32_t)vgmstream->current_block_offset); - memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample)); + memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t)); break; } if (vgmstream->current_block_offset < 0 || vgmstream->current_block_offset == 0xFFFFFFFF) { /* probably block bug or EOF, block functions won't be able to read anything useful/infinite loop */ VGM_LOG("layout_blocked: wrong block offset found\n"); - memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample)); + memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t)); break; } diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_schl.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_schl.c index 7c99b6b2e..7b4b024e4 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_schl.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked_ea_schl.c @@ -10,6 +10,10 @@ void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) { size_t block_size, block_samples; int32_t (*read_32bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_32bitBE : read_32bitLE; + uint32_t flag_lang = (vgmstream->codec_config >> 16) & 0xFFFF; + int flag_be = (vgmstream->codec_config & 0x02); + int flag_adpcm = (vgmstream->codec_config & 0x01); + /* EOF reads: signal we have nothing and let the layout fail */ if (block_offset >= get_streamfile_size(streamFile)) { @@ -23,35 +27,21 @@ void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) { { uint32_t block_id = read_32bitBE(block_offset+0x00,streamFile); - if (vgmstream->codec_config & 0x02) /* size is always LE, except in early SS/MAC */ + if (flag_be) /* size is always LE, except in early SS/MAC */ block_size = read_32bitBE(block_offset + 0x04,streamFile); else block_size = read_32bitLE(block_offset + 0x04,streamFile); - switch(block_id) { - case 0x5343446C: /* "SCDl" */ - case 0x5344454E: /* "SDEN" */ - case 0x53444652: /* "SDFR" */ - case 0x53444745: /* "SDGE" */ - case 0x53444445: /* "SDDE" */ - case 0x53444954: /* "SDIT" */ - case 0x53445350: /* "SDSP" */ - case 0x53444553: /* "SDES" */ - case 0x53444D58: /* "SDMX" */ - case 0x53445255: /* "SDRU" */ - case 0x53444A41: /* "SDJA" */ - case 0x53444A50: /* "SDJP" */ - case 0x5344504C: /* "SDPL" */ - /* audio chunk */ - if (vgmstream->coding_type == coding_PSX) - block_samples = ps_bytes_to_samples(block_size-0x10, vgmstream->channels); - else - block_samples = read_32bit(block_offset+0x08,streamFile); - break; - default: - /* ignore other chunks (audio "SCHl/SCCl/...", video "pIQT/MADk/...", etc) */ - block_samples = 0; /* layout ignores this */ - break; + if (block_id == 0x5343446C || block_id == (0x53440000 | flag_lang)) { + /* "SCDl" or "SDxx" audio chunk */ + if (vgmstream->coding_type == coding_PSX) + block_samples = ps_bytes_to_samples(block_size-0x10, vgmstream->channels); + else + block_samples = read_32bit(block_offset+0x08,streamFile); + } + else { + /* ignore other chunks (audio "SCHl/SCCl/...", non-target lang, video "pIQT/MADk/...", etc) */ + block_samples = 0; /* layout ignores this */ } /* "SCHl" start block (movie "SHxx" shouldn't use multi files) */ @@ -185,7 +175,7 @@ void block_update_ea_schl(off_t block_offset, VGMSTREAM * vgmstream) { } /* read ADPCM history before each channel if needed (not actually read in sx.exe) */ - if (vgmstream->codec_config & 0x01) { + if (flag_adpcm) { for (i = 0; i < vgmstream->channels; i++) { //vgmstream->ch[i].adpcm_history1_32 = read_16bit(vgmstream->ch[i].offset+0x00,streamFile); //vgmstream->ch[i].adpcm_history3_32 = read_16bit(vgmstream->ch[i].offset+0x02,streamFile); diff --git a/Frameworks/vgmstream/vgmstream/src/layout/blocked_wsi.c b/Frameworks/vgmstream/vgmstream/src/layout/blocked_wsi.c index 6be576fb4..c493f1f34 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/blocked_wsi.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/blocked_wsi.c @@ -6,7 +6,6 @@ void block_update_wsi(off_t block_offset, VGMSTREAM * vgmstream) { STREAMFILE* streamFile = vgmstream->ch[0].streamfile; int i; off_t channel_block_size; - //int is_first_offset = /* assume that all channels have the same size for this block */ @@ -22,8 +21,6 @@ void block_update_wsi(off_t block_offset, VGMSTREAM * vgmstream) { /* first block has DSP header, remove */ if (block_offset == vgmstream->ch[0].channel_start_offset) { - int i; - vgmstream->current_block_size -= 0x60; for (i = 0; i < vgmstream->channels; i++) { vgmstream->ch[i].offset += 0x60; diff --git a/Frameworks/vgmstream/vgmstream/src/layout/flat.c b/Frameworks/vgmstream/vgmstream/src/layout/flat.c index 59f3ce034..ae5b91e9e 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/flat.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/flat.c @@ -4,7 +4,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 * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { +void render_vgmstream_flat(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { int samples_written = 0; int samples_per_frame, samples_this_block; @@ -23,10 +23,11 @@ void render_vgmstream_flat(sample * buffer, int32_t sample_count, VGMSTREAM * vg samples_to_do = vgmstream_samples_to_do(samples_this_block, samples_per_frame, vgmstream); 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 found\n"); - memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample)); + 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; } diff --git a/Frameworks/vgmstream/vgmstream/src/layout/interleave.c b/Frameworks/vgmstream/vgmstream/src/layout/interleave.c index 9e64fdfd2..d61cc3e2b 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/interleave.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/interleave.c @@ -6,7 +6,7 @@ * Data has interleaved chunks per channel, and once one is decoded the layout moves offsets, * skipping other chunks (essentially a simplified variety of blocked layout). * Incompatible with decoders that move offsets. */ -void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { +void render_vgmstream_interleave(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { int samples_written = 0; int frame_size, samples_per_frame, samples_this_block; int has_interleave_last = vgmstream->interleave_last_block_size && vgmstream->channels > 1; @@ -49,7 +49,7 @@ void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREA if (samples_to_do == 0) { /* happens when interleave is not set */ VGM_LOG("layout_interleave: wrong samples_to_do found\n"); - memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample)); + memset(buffer + samples_written*vgmstream->channels, 0, (sample_count - samples_written) * vgmstream->channels * sizeof(sample_t)); break; } diff --git a/Frameworks/vgmstream/vgmstream/src/layout/layered.c b/Frameworks/vgmstream/vgmstream/src/layout/layered.c index 2dcae87bb..af4ebd1ef 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/layered.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/layered.c @@ -1,50 +1,58 @@ #include "layout.h" #include "../vgmstream.h" +#include "../mixing.h" /* NOTE: if loop settings change the layered vgmstreams must be notified (preferably using vgmstream_force_loop) */ -#define LAYER_BUF_SIZE 512 -#define LAYER_MAX_CHANNELS 6 /* at least 2, but let's be generous */ +#define VGMSTREAM_MAX_LAYERS 255 +#define VGMSTREAM_LAYER_SAMPLE_BUFFER 8192 + /* Decodes samples for layered streams. * Similar to interleave layout, but decodec samples are mixed from complete vgmstreams, each * with custom codecs and different number of channels, creating a single super-vgmstream. * Usually combined with custom streamfiles to handle data interleaved in weird ways. */ -void render_vgmstream_layered(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { +void render_vgmstream_layered(sample_t * outbuf, int32_t sample_count, VGMSTREAM * vgmstream) { int samples_written = 0; layered_layout_data *data = vgmstream->layout_data; - sample interleave_buf[LAYER_BUF_SIZE*LAYER_MAX_CHANNELS]; while (samples_written < sample_count) { - int samples_to_do = LAYER_BUF_SIZE; + int samples_to_do = VGMSTREAM_LAYER_SAMPLE_BUFFER; int layer, ch = 0; if (samples_to_do > sample_count - samples_written) samples_to_do = sample_count - samples_written; for (layer = 0; layer < data->layer_count; layer++) { - int s, layer_ch; - int layer_channels = data->layers[layer]->channels; + int s, layer_ch, layer_channels; - /* each layer will handle its own looping internally */ + /* each layer will handle its own looping/mixing internally */ - render_vgmstream(interleave_buf, samples_to_do, data->layers[layer]); + /* layers may have its own number of channels */ + mixing_info(data->layers[layer], NULL, &layer_channels); + + render_vgmstream( + data->buffer, + samples_to_do, + data->layers[layer]); /* mix layer samples to main samples */ for (layer_ch = 0; layer_ch < layer_channels; layer_ch++) { for (s = 0; s < samples_to_do; s++) { size_t layer_sample = s*layer_channels + layer_ch; - size_t buffer_sample = (samples_written+s)*vgmstream->channels + ch; + size_t buffer_sample = (samples_written+s)*data->output_channels + ch; - buffer[buffer_sample] = interleave_buf[layer_sample]; + outbuf[buffer_sample] = data->buffer[layer_sample]; } ch++; } } samples_written += samples_to_do; - vgmstream->current_sample = data->layers[0]->current_sample; /* just in case it's used for info */ + /* needed for info (ex. for mixing) */ + vgmstream->current_sample = data->layers[0]->current_sample; + vgmstream->loop_count = data->layers[0]->loop_count; //vgmstream->samples_into_block = 0; /* handled in each layer */ } } @@ -53,7 +61,7 @@ void render_vgmstream_layered(sample * buffer, int32_t sample_count, VGMSTREAM * layered_layout_data* init_layout_layered(int layer_count) { layered_layout_data *data = NULL; - if (layer_count <= 0 || layer_count > 255) + if (layer_count <= 0 || layer_count > VGMSTREAM_MAX_LAYERS) goto fail; data = calloc(1, sizeof(layered_layout_data)); @@ -71,38 +79,62 @@ fail: } int setup_layout_layered(layered_layout_data* data) { - int i; + int i, max_input_channels = 0, max_output_channels = 0; + sample_t *outbuf_re = NULL; + /* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */ for (i = 0; i < data->layer_count; i++) { - if (!data->layers[i]) - goto fail; + int layer_input_channels, layer_output_channels; - if (data->layers[i]->num_samples <= 0) + if (data->layers[i] == NULL) { + VGM_LOG("layered: no vgmstream in %i\n", i); goto fail; + } - if (data->layers[i]->channels > LAYER_MAX_CHANNELS) + if (data->layers[i]->num_samples <= 0) { + VGM_LOG("layered: no samples in %i\n", i); goto fail; + } + + /* different layers may have different input/output channels */ + mixing_info(data->layers[i], &layer_input_channels, &layer_output_channels); + + max_output_channels += layer_output_channels; + if (max_input_channels < layer_input_channels) + max_input_channels = layer_input_channels; if (i > 0) { /* a bit weird, but no matter */ if (data->layers[i]->sample_rate != data->layers[i-1]->sample_rate) { - VGM_LOG("layered layout: layer %i has different sample rate\n", i); + VGM_LOG("layered: layer %i has different sample rate\n", i); } /* also weird */ if (data->layers[i]->coding_type != data->layers[i-1]->coding_type) { - VGM_LOG("layered layout: layer %i has different coding type\n", i); + VGM_LOG("layered: layer %i has different coding type\n", i); } } - //todo could check if layers'd loop match vs main, etc + /* loops and other values could be mismatched but hopefully not */ - /* save start things so we can restart for seeking/looping */ - memcpy(data->layers[i]->start_ch,data->layers[i]->ch,sizeof(VGMSTREAMCHANNEL)*data->layers[i]->channels); - memcpy(data->layers[i]->start_vgmstream,data->layers[i],sizeof(VGMSTREAM)); + + setup_vgmstream(data->layers[i]); /* final setup in case the VGMSTREAM was created manually */ + + mixing_setup(data->layers[i], VGMSTREAM_LAYER_SAMPLE_BUFFER); /* init mixing */ } + if (max_output_channels > VGMSTREAM_MAX_CHANNELS || max_input_channels > VGMSTREAM_MAX_CHANNELS) + goto fail; + + /* create internal buffer big enough for mixing */ + outbuf_re = realloc(data->buffer, VGMSTREAM_LAYER_SAMPLE_BUFFER*max_input_channels*sizeof(sample_t)); + if (!outbuf_re) goto fail; + data->buffer = outbuf_re; + + data->input_channels = max_input_channels; + data->output_channels = max_output_channels; + return 1; fail: return 0; /* caller is expected to free */ @@ -133,3 +165,39 @@ void reset_layout_layered(layered_layout_data *data) { reset_vgmstream(data->layers[i]); } } + +/* helper for easier creation of layers */ +VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data) { + VGMSTREAM *vgmstream = NULL; + int i, channels, loop_flag; + + /* get data */ + channels = data->output_channels; + loop_flag = 1; + for (i = 0; i < data->layer_count; i++) { + if (loop_flag && !data->layers[i]->loop_flag) + loop_flag = 0; + } + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = data->layers[0]->meta_type; + vgmstream->sample_rate = data->layers[0]->sample_rate; + vgmstream->num_samples = data->layers[0]->num_samples; + vgmstream->loop_start_sample = data->layers[0]->loop_start_sample; + vgmstream->loop_end_sample = data->layers[0]->loop_end_sample; + vgmstream->coding_type = data->layers[0]->coding_type; + + vgmstream->layout_type = layout_layered; + vgmstream->layout_data = data; + + return vgmstream; + +fail: + if (vgmstream) vgmstream->layout_data = NULL; + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/layout/layout.h b/Frameworks/vgmstream/vgmstream/src/layout/layout.h index 6e3b65981..e9003c8f3 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/layout.h +++ b/Frameworks/vgmstream/vgmstream/src/layout/layout.h @@ -5,7 +5,7 @@ #include "../vgmstream.h" /* blocked layouts */ -void render_vgmstream_blocked(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream); +void render_vgmstream_blocked(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream); void block_update(off_t block_offset, VGMSTREAM * vgmstream); void block_update_ast(off_t block_ofset, VGMSTREAM * vgmstream); @@ -48,22 +48,22 @@ void block_update_xa_aiff(off_t block_offset, VGMSTREAM * vgmstream); void block_update_vs_square(off_t block_offset, VGMSTREAM * vgmstream); /* other layouts */ -void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream); +void render_vgmstream_interleave(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream); -void render_vgmstream_flat(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream); +void render_vgmstream_flat(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream); -void render_vgmstream_aix(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream); - -void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream); +void render_vgmstream_segmented(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream); segmented_layout_data* init_layout_segmented(int segment_count); int setup_layout_segmented(segmented_layout_data* data); void free_layout_segmented(segmented_layout_data *data); void reset_layout_segmented(segmented_layout_data *data); +VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment); -void render_vgmstream_layered(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream); +void render_vgmstream_layered(sample_t * buffer, int32_t sample_count, VGMSTREAM * vgmstream); layered_layout_data* init_layout_layered(int layer_count); int setup_layout_layered(layered_layout_data* data); void free_layout_layered(layered_layout_data *data); void reset_layout_layered(layered_layout_data *data); +VGMSTREAM *allocate_layered_vgmstream(layered_layout_data* data); #endif diff --git a/Frameworks/vgmstream/vgmstream/src/layout/segmented.c b/Frameworks/vgmstream/vgmstream/src/layout/segmented.c index ff87c68e3..2c2e18c8c 100644 --- a/Frameworks/vgmstream/vgmstream/src/layout/segmented.c +++ b/Frameworks/vgmstream/vgmstream/src/layout/segmented.c @@ -1,50 +1,74 @@ #include "layout.h" #include "../vgmstream.h" +#include "../mixing.h" + +#define VGMSTREAM_MAX_SEGMENTS 255 +#define VGMSTREAM_SEGMENT_SAMPLE_BUFFER 8192 /* Decodes samples for segmented streams. * Chains together sequential vgmstreams, for data divided into separate sections or files * (like one part for intro and other for loop segments, which may even use different codecs). */ -void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream) { - int samples_written = 0; +void render_vgmstream_segmented(sample_t * outbuf, int32_t sample_count, VGMSTREAM * vgmstream) { + int samples_written = 0, loop_samples_skip = 0; segmented_layout_data *data = vgmstream->layout_data; + int use_internal_buffer = 0; + + + /* normally uses outbuf directly (faster) but could need internal buffer if downmixing */ + if (vgmstream->channels != data->input_channels) { + use_internal_buffer = 1; + } while (samples_written < sample_count) { int samples_to_do; - int samples_this_block = data->segments[data->current_segment]->num_samples; - + int samples_this_segment = data->segments[data->current_segment]->num_samples; if (vgmstream->loop_flag && vgmstream_do_loop(vgmstream)) { - /* handle looping, finding loop segment */ - int loop_segment = 0, samples = 0, loop_samples_skip = 0; - while (samples < vgmstream->num_samples) { + int segment, loop_segment, total_samples; + + /* handle looping by finding loop segment and loop_start inside that segment */ + loop_segment = 0; + total_samples = 0; + while (total_samples < vgmstream->num_samples) { int32_t segment_samples = data->segments[loop_segment]->num_samples; - if (vgmstream->loop_start_sample >= samples && vgmstream->loop_start_sample < samples + segment_samples) { - loop_samples_skip = vgmstream->loop_start_sample - samples; + + if (vgmstream->loop_sample >= total_samples && vgmstream->loop_sample < total_samples + segment_samples) { + loop_samples_skip = vgmstream->loop_sample - total_samples; break; /* loop_start falls within loop_segment's samples */ } - samples += segment_samples; + total_samples += segment_samples; loop_segment++; } + if (loop_segment == data->segment_count) { VGM_LOG("segmented_layout: can't find loop segment\n"); loop_segment = 0; } - if (loop_samples_skip > 0) { - VGM_LOG("segmented_layout: loop starts after %i samples\n", loop_samples_skip); - //todo skip/fix, but probably won't happen - } data->current_segment = loop_segment; - reset_vgmstream(data->segments[data->current_segment]); + + /* loops can span multiple segments */ + for (segment = loop_segment; segment < data->segment_count; segment++) { + reset_vgmstream(data->segments[segment]); + } + vgmstream->samples_into_block = 0; continue; } - samples_to_do = vgmstream_samples_to_do(samples_this_block, sample_count, vgmstream); + samples_to_do = vgmstream_samples_to_do(samples_this_segment, sample_count, vgmstream); if (samples_to_do > sample_count - samples_written) samples_to_do = sample_count - samples_written; + if (samples_to_do > VGMSTREAM_SEGMENT_SAMPLE_BUFFER /*&& use_internal_buffer*/) /* always for fade/etc mixes */ + samples_to_do = VGMSTREAM_SEGMENT_SAMPLE_BUFFER; + + /* segment looping: discard until actual start */ + if (loop_samples_skip > 0) { + if (samples_to_do > loop_samples_skip) + samples_to_do = loop_samples_skip; + } /* detect segment change and restart */ if (samples_to_do == 0) { @@ -54,8 +78,25 @@ void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM continue; } - render_vgmstream(&buffer[samples_written*data->segments[data->current_segment]->channels], - samples_to_do,data->segments[data->current_segment]); + render_vgmstream( + use_internal_buffer ? + data->buffer : + &outbuf[samples_written * data->output_channels], + samples_to_do, + data->segments[data->current_segment]); + + if (loop_samples_skip > 0) { + loop_samples_skip -= samples_to_do; + vgmstream->samples_into_block += samples_to_do; + continue; + } + + if (use_internal_buffer) { + int s; + for (s = 0; s < samples_to_do * data->output_channels; s++) { + outbuf[samples_written * data->output_channels + s] = data->buffer[s]; + } + } samples_written += samples_to_do; vgmstream->current_sample += samples_to_do; @@ -67,7 +108,7 @@ void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM segmented_layout_data* init_layout_segmented(int segment_count) { segmented_layout_data *data = NULL; - if (segment_count <= 0 || segment_count > 255) + if (segment_count <= 0 || segment_count > VGMSTREAM_MAX_SEGMENTS) goto fail; data = calloc(1, sizeof(segmented_layout_data)); @@ -86,41 +127,75 @@ fail: } int setup_layout_segmented(segmented_layout_data* data) { - int i; + int i, max_input_channels = 0, max_output_channels = 0; + sample_t *outbuf_re = NULL; + /* setup each VGMSTREAM (roughly equivalent to vgmstream.c's init_vgmstream_internal stuff) */ for (i = 0; i < data->segment_count; i++) { - if (!data->segments[i]) - goto fail; + int segment_input_channels, segment_output_channels; - if (data->segments[i]->num_samples <= 0) + if (data->segments[i] == NULL) { + VGM_LOG("segmented: no vgmstream in segment %i\n", i); goto fail; + } - /* shouldn't happen */ + + if (data->segments[i]->num_samples <= 0) { + VGM_LOG("segmented: no samples in segment %i\n", i); + goto fail; + } + + + /* disable so that looping is controlled by render_vgmstream_segmented */ if (data->segments[i]->loop_flag != 0) { - VGM_LOG("segmented layout: segment %i is looped\n", i); + VGM_LOG("segmented: segment %i is looped\n", i); data->segments[i]->loop_flag = 0; } + /* different segments may have different input channels, though output should be + * the same for all (ex. 2ch + 1ch segments, but 2ch segment is downmixed to 1ch) */ + mixing_info(data->segments[i], &segment_input_channels, &segment_output_channels); + if (max_input_channels < segment_input_channels) + max_input_channels = segment_input_channels; + if (max_output_channels < segment_output_channels) + max_output_channels = segment_output_channels; + if (i > 0) { - if (data->segments[i]->channels != data->segments[i-1]->channels) + int prev_output_channels; + + mixing_info(data->segments[i-1], NULL, &prev_output_channels); + if (segment_output_channels != prev_output_channels) { + VGM_LOG("segmented: segment %i has wrong channels %i vs prev channels %i\n", i, segment_output_channels, prev_output_channels); goto fail; + } /* a bit weird, but no matter */ if (data->segments[i]->sample_rate != data->segments[i-1]->sample_rate) { - VGM_LOG("segmented layout: segment %i has different sample rate\n", i); + VGM_LOG("segmented: segment %i has different sample rate\n", i); } + /* perfectly acceptable */ //if (data->segments[i]->coding_type != data->segments[i-1]->coding_type) - // goto fail; /* perfectly acceptable */ + // goto fail; } - /* save start things so we can restart for seeking/looping */ - memcpy(data->segments[i]->start_ch,data->segments[i]->ch,sizeof(VGMSTREAMCHANNEL)*data->segments[i]->channels); - memcpy(data->segments[i]->start_vgmstream,data->segments[i],sizeof(VGMSTREAM)); + setup_vgmstream(data->segments[i]); /* final setup in case the VGMSTREAM was created manually */ + + mixing_setup(data->segments[i], VGMSTREAM_SEGMENT_SAMPLE_BUFFER); /* init mixing */ } + if (max_output_channels > VGMSTREAM_MAX_CHANNELS || max_input_channels > VGMSTREAM_MAX_CHANNELS) + goto fail; + + /* create internal buffer big enough for mixing */ + outbuf_re = realloc(data->buffer, VGMSTREAM_SEGMENT_SAMPLE_BUFFER*max_input_channels*sizeof(sample_t)); + if (!outbuf_re) goto fail; + data->buffer = outbuf_re; + + data->input_channels = max_input_channels; + data->output_channels = max_output_channels; return 1; fail: @@ -153,3 +228,53 @@ void reset_layout_segmented(segmented_layout_data *data) { reset_vgmstream(data->segments[i]); } } + +/* helper for easier creation of segments */ +VGMSTREAM *allocate_segmented_vgmstream(segmented_layout_data* data, int loop_flag, int loop_start_segment, int loop_end_segment) { + VGMSTREAM *vgmstream = NULL; + int channel_layout; + int i, num_samples, loop_start, loop_end; + + /* save data */ + channel_layout = data->segments[0]->channel_layout; + num_samples = 0; + loop_start = 0; + loop_end = 0; + for (i = 0; i < data->segment_count; i++) { + if (loop_flag && i == loop_start_segment) + loop_start = num_samples; + + num_samples += data->segments[i]->num_samples; + + if (loop_flag && i == loop_end_segment) + loop_end = num_samples; + + /* inherit first segment's layout but only if all segments' layout match */ + if (channel_layout != 0 && channel_layout != data->segments[i]->channel_layout) + channel_layout = 0; + } + + /* respect loop_flag even when no loop_end found as it's possible file loops are set outside */ + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(data->output_channels, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = data->segments[0]->meta_type; + vgmstream->sample_rate = data->segments[0]->sample_rate; + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + vgmstream->coding_type = data->segments[0]->coding_type; + vgmstream->channel_layout = channel_layout; + + vgmstream->layout_type = layout_segmented; + vgmstream->layout_data = data; + + return vgmstream; + +fail: + if (vgmstream) vgmstream->layout_data = NULL; + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/9tav.c b/Frameworks/vgmstream/vgmstream/src/meta/9tav.c new file mode 100644 index 000000000..a2a0d6959 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/9tav.c @@ -0,0 +1,118 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "../layout/layout.h" +#include "9tav_streamfile.h" + +/* 9TAV - from Metal Gear Solid 2/3 HD (Vita) */ +VGMSTREAM * init_vgmstream_9tav(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate, track_count; + int32_t num_samples, loop_start, loop_end; + size_t track_size; + uint32_t config_data; + int i, is_padded; + layered_layout_data * data = NULL; + STREAMFILE* temp_streamFile = NULL; + + + /* checks */ + /* .9tav: header id */ + if (!check_extensions(streamFile, "9tav")) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x39544156) /* "9TAV" */ + goto fail; + + /* 0x04: always 0x09 */ + channel_count = read_16bitLE(0x08,streamFile); + track_count = read_16bitLE(0x0a,streamFile); /* MGS3 uses multitracks */ + sample_rate = read_32bitLE(0x0c,streamFile); + track_size = read_32bitLE(0x10,streamFile); /* without padding */ + //data_size = read_32bitLE(0x14,streamFile); /* without padding */ + num_samples = read_32bitLE(0x18,streamFile); + config_data = read_32bitBE(0x1c,streamFile); + + + if (read_32bitBE(0x20,streamFile) == 0x4D544146) { /* "MTAF" */ + /* MGS3 has a MTAF header (data size and stuff don't match, probably for track info) */ + loop_start = read_32bitLE(0x78, streamFile); + loop_end = read_32bitLE(0x7c, streamFile); + loop_flag = read_32bitLE(0x90, streamFile) & 1; + + is_padded = 1; /* data also has padding and other oddities */ + start_offset = 0x00; + } + else { + /* MGS2 doesn't */ + loop_start = 0; + loop_end = 0; + loop_flag = 0; + + is_padded = 0; + start_offset = 0x20; + } + + + /* init layout */ + data = init_layout_layered(track_count); + if (!data) goto fail; + + /* open each layer subfile */ + for (i = 0; i < data->layer_count; i++) { + data->layers[i] = allocate_vgmstream(channel_count, loop_flag); + if (!data->layers[i]) goto fail; + + data->layers[i]->meta_type = meta_9TAV; + data->layers[i]->sample_rate = sample_rate; + data->layers[i]->num_samples = num_samples; + data->layers[i]->loop_start_sample = loop_start; + data->layers[i]->loop_end_sample = loop_end; + +#ifdef VGM_USE_ATRAC9 + { + atrac9_config cfg = {0}; + cfg.channels = channel_count; + cfg.config_data = config_data; + cfg.encoder_delay = atrac9_bytes_to_samples_cfg(track_size, cfg.config_data) - num_samples; /* seems ok */ + if (cfg.encoder_delay > 4096) /* doesn't seem too normal */ + cfg.encoder_delay = 0; + + data->layers[i]->codec_data = init_atrac9(&cfg); + if (!data->layers[i]->codec_data) goto fail; + data->layers[i]->coding_type = coding_ATRAC9; + data->layers[i]->layout_type = layout_none; + } +#else + goto fail; +#endif + + if (is_padded) { + temp_streamFile = setup_9tav_streamfile(streamFile, 0xFE4, track_size, i, track_count); + if (!temp_streamFile) goto fail; + } + + if (!vgmstream_open_stream(data->layers[i],temp_streamFile == NULL ? streamFile : temp_streamFile,start_offset)) + goto fail; + + close_streamfile(temp_streamFile); + temp_streamFile = NULL; + } + + /* setup layered VGMSTREAMs */ + if (!setup_layout_layered(data)) + goto fail; + + /* build the layered VGMSTREAM */ + vgmstream = allocate_layered_vgmstream(data); + if (!vgmstream) goto fail; + + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + if (!vgmstream) + free_layout_layered(data); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/9tav_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/9tav_streamfile.h new file mode 100644 index 000000000..9cc891357 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/9tav_streamfile.h @@ -0,0 +1,182 @@ +#ifndef _9TAV_STREAMFILE_H_ +#define _9TAV_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + /* config */ + off_t stream_offset; + size_t stream_size; + size_t track_size; + int track_number; + int track_count; + int skip_count; + int read_count; + size_t frame_size; + size_t interleave_count; + size_t interleave_last_count; + + /* state */ + off_t logical_offset; /* fake offset */ + off_t physical_offset; /* actual offset */ + size_t block_size; /* current size */ + size_t skip_size; /* size from block start to reach data */ + size_t data_size; /* usable size in a block */ + + size_t logical_size; +} ntav_io_data; + + +static size_t ntav_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, ntav_io_data* data) { + size_t total_read = 0; + + + /* re-start when previous offset (can't map logical<>physical offsets) */ + if (data->logical_offset < 0 || offset < data->logical_offset) { + data->physical_offset = data->stream_offset; + data->logical_offset = 0x00; + data->data_size = 0; + data->skip_size = 0; + data->read_count = 0; + data->skip_count = data->interleave_count * data->track_number; + //VGM_LOG("0 o=%lx, sc=%i\n", data->physical_offset, data->skip_count); + } + + /* read blocks */ + while (length > 0) { + //VGM_LOG("1 of=%lx, so=%lx, sz=%x, of2=%lx, log=%lx\n", data->physical_offset, data->stream_offset, data->stream_size, offset, data->logical_offset); + + /* ignore EOF */ + if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { + //VGM_LOG("9 o=%lx, so=%lx, sz=%x, of2=%lx, log=%lx\n", data->physical_offset, data->stream_offset, data->stream_size, offset, data->logical_offset); + //VGM_LOG("eof\n"); + break; + } + + + /* process new block */ + if (data->data_size == 0) { + /* not very exact compared to real blocks but ok enough */ + if (read_32bitLE(data->physical_offset, streamfile) == 0x00) { + data->block_size = 0x10; + //VGM_LOG("1 o=%lx, lo=%lx skip\n", data->physical_offset, data->logical_offset); + } + else { + data->block_size = data->frame_size; + + //VGM_LOG("2 o=%lx, lo=%lx, skip=%i, read=%i\n", data->physical_offset, data->logical_offset, data->skip_count, data->read_count); + + /* each track interleaves NTAV_INTERLEAVE frames, but can contain padding in between, + * so must read one by one up to max */ + + if (data->skip_count == 0 && data->read_count == 0) { + data->read_count = data->interleave_count; + } + + if (data->skip_count) { + data->skip_count--; + } + + if (data->read_count) { + data->data_size = data->block_size; + data->read_count--; + + if (data->read_count == 0) { + if (data->logical_offset + data->interleave_count * data->frame_size > data->track_size) + data->skip_count = data->interleave_last_count * (data->track_count - 1); + else + data->skip_count = data->interleave_count * (data->track_count - 1); + } + } + + } + } + + /* move to next block */ + if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size; + data->data_size = 0; + continue; + } + + /* read data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + +static size_t ntav_io_size(STREAMFILE *streamfile, ntav_io_data* data) { + uint8_t buf[1]; + + if (data->logical_size) + return data->logical_size; + + /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ + ntav_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles deinterleaving of 9TAV blocked streams. Unlike other games using .sdt, + * KCEJ blocks have a data_size field and rest is padding. Even after that all blocks start + * with 0 (skipped) and there are padding blocks that start with LE 0xDEADBEEF. + * This streamfile handles 9tav extracted like regular sdt and remove padding manually. */ +static STREAMFILE* setup_9tav_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t track_size, int track_number, int track_count) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + ntav_io_data io_data = {0}; + size_t io_data_size = sizeof(ntav_io_data); + size_t last_size; + + io_data.stream_offset = stream_offset; + io_data.stream_size = get_streamfile_size(streamFile) - stream_offset; + io_data.track_size = track_size; + io_data.track_number = track_number; + io_data.track_count = track_count; + io_data.frame_size = 0x40; + io_data.interleave_count = 256; + last_size = track_size % (io_data.interleave_count * io_data.frame_size); + if (last_size) + io_data.interleave_last_count = last_size / io_data.frame_size; + io_data.logical_offset = -1; /* force state reset */ + + /* setup subfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, ntav_io_read,ntav_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_buffer_streamfile(new_streamFile,0); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +#endif /* _9TAV_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h index f603c8582..bee23ec61 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/adx_keys.h @@ -188,6 +188,9 @@ static const adxkey_info adxkey8_list[] = { /* Lucky Star - Ryouou Gakuen Outousai (PS2) */ {0x481D,0x44F9,0x4E35, "LSTARPS2",0}, + /* Bakumatsu Renka: Shinsengumi (PS2) */ + {0x5381,0x5701,0x665B, "SHINN",0}, + }; static const adxkey_info adxkey9_list[] = { diff --git a/Frameworks/vgmstream/vgmstream/src/meta/aif_asobo.c b/Frameworks/vgmstream/vgmstream/src/meta/aif_asobo.c index 77cef8bee..9610dc67a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/aif_asobo.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/aif_asobo.c @@ -10,8 +10,8 @@ VGMSTREAM * init_vgmstream_aif_asobo(STREAMFILE *streamFile) { /* checks */ - /* aif: standard, aiffl: for plugins? */ - if ( !check_extensions(streamFile,"aif,aiffl") ) + /* aif: standard, .laif/aiffl: for plugins */ + if ( !check_extensions(streamFile,"aif,laif,aiffl") ) goto fail; if ((uint16_t)read_16bitLE(0x00,streamFile) != 0x69) /* Xbox codec */ goto fail; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/aifc.c b/Frameworks/vgmstream/vgmstream/src/meta/aifc.c index daf2663c2..c51a408d7 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/aifc.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/aifc.c @@ -67,20 +67,21 @@ VGMSTREAM * init_vgmstream_aifc(STREAMFILE *streamFile) { /* checks */ /* .aif: common (AIFF or AIFC), .aiff: common AIFF, .aifc: common AIFC + * .laif/laifc/laiff: for plugins + * .aifcl/aiffl: for plugins? * .cbd2: M2 games * .bgm: Super Street Fighter II Turbo (3DO) * .acm: Crusader - No Remorse (SAT) * .adp: Sonic Jam (SAT) - * .ai: Dragon Force (SAT) - * .aifcl/aiffl: for plugins? */ - if (check_extensions(streamFile, "aif")) { + * .ai: Dragon Force (SAT) */ + if (check_extensions(streamFile, "aif,laif")) { is_aifc_ext = 1; is_aiff_ext = 1; } - else if (check_extensions(streamFile, "aifc,aifcl,afc,cbd2,bgm")) { + else if (check_extensions(streamFile, "aifc,laifc,aifcl,afc,cbd2,bgm")) { is_aifc_ext = 1; } - else if (check_extensions(streamFile, "aiff,acm,adp,ai,aiffl")) { + else if (check_extensions(streamFile, "aiff,laiff,acm,adp,ai,aiffl")) { is_aiff_ext = 1; } else { diff --git a/Frameworks/vgmstream/vgmstream/src/meta/aix.c b/Frameworks/vgmstream/vgmstream/src/meta/aix.c index 57b429eb8..93ab5dc46 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/aix.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/aix.c @@ -1,200 +1,195 @@ #include "meta.h" +#include "../layout/layout.h" #include "aix_streamfile.h" -/* AIX - interleaved AAX, N segments with M layers (1/2ch) inside [SoulCalibur IV (PS3), Dragon Ball Z: Burst Limit (PS3)] */ -VGMSTREAM * init_vgmstream_aix(STREAMFILE *streamFile) { + +#define MAX_SEGMENTS 50 /* usually segment0=intro, segment1=loop/main, sometimes ~5, rarely ~40 */ + +static VGMSTREAM *build_segmented_vgmstream(STREAMFILE *streamFile, off_t *segment_offsets, size_t *segment_sizes, int32_t *segment_samples, int segment_count, int layer_count); + +/* AIX - N segments with M layers (2ch ADX) inside [SoulCalibur IV (PS3), Dragon Ball Z: Burst Limit (PS3)] */ +VGMSTREAM * init_vgmstream_aix(STREAMFILE *sf) { VGMSTREAM * vgmstream = NULL; - STREAMFILE * streamFileAIX = NULL; - int loop_flag = 0, channel_count, sample_rate; - int32_t sample_count, loop_start_sample = 0, loop_end_sample = 0; - off_t *segment_offset = NULL; - int32_t *segment_samples = NULL; - aix_codec_data *data = NULL; - off_t data_offset; - off_t layer_list_offset; - off_t layer_list_end; - const off_t segment_list_offset = 0x20; - const size_t segment_list_entry_size = 0x10; - const size_t layer_list_entry_size = 0x08; + off_t segment_offsets[MAX_SEGMENTS] = {0}; + size_t segment_sizes[MAX_SEGMENTS] = {0}; + int32_t segment_samples[MAX_SEGMENTS] = {0}; + int segment_rates[MAX_SEGMENTS] = {0}; + off_t data_offset, subtable_offset; int segment_count, layer_count; - int i, j; + int i; /* checks */ - if (!check_extensions(streamFile, "aix")) + if (!check_extensions(sf, "aix")) goto fail; - if (read_32bitBE(0x00,streamFile) != 0x41495846 || /* "AIXF" */ - read_32bitBE(0x08,streamFile) != 0x01000014 || /* version? */ - read_32bitBE(0x0c,streamFile) != 0x00000800) + if (read_u32be(0x00,sf) != 0x41495846 || /* "AIXF" */ + read_u32be(0x08,sf) != 0x01000014 || /* version? */ + read_u32be(0x0c,sf) != 0x00000800) goto fail; + /* AIX combine layers for multichannel and segments for looping, all very hacky. + * For some reason AIX with 1 layer and 1 segment exist (equivalent to a single ADX). */ + /* base segment header */ - data_offset = read_32bitBE(0x04,streamFile)+0x08; - - segment_count = (uint16_t)read_16bitBE(0x18,streamFile); - if (segment_count < 1) goto fail; - - layer_list_offset = segment_list_offset + segment_count*segment_list_entry_size + 0x10; - if (layer_list_offset >= data_offset) goto fail; - - segment_samples = calloc(segment_count,sizeof(int32_t)); - if (!segment_samples) goto fail; - segment_offset = calloc(segment_count,sizeof(off_t)); - if (!segment_offset) goto fail; + data_offset = read_s32be(0x04,sf) + 0x08; /* parse segments table */ { - sample_rate = read_32bitBE(layer_list_offset+0x08,streamFile); /* first layer's sample rate */ + const off_t segment_list_offset = 0x20; + const size_t segment_list_entry_size = 0x10; + + segment_count = read_u16be(0x18,sf); + if (segment_count < 1 || segment_count > MAX_SEGMENTS) goto fail; + + subtable_offset = segment_list_offset + segment_count*segment_list_entry_size; + if (subtable_offset >= data_offset) goto fail; for (i = 0; i < segment_count; i++) { - segment_offset[i] = read_32bitBE(segment_list_offset + segment_list_entry_size*i + 0x00,streamFile); - /* 0x04: segment size */ - segment_samples[i] = read_32bitBE(segment_list_offset + segment_list_entry_size*i + 0x08,streamFile); + segment_offsets[i] = read_s32be(segment_list_offset + segment_list_entry_size*i + 0x00,sf); + segment_sizes[i] = read_u32be(segment_list_offset + segment_list_entry_size*i + 0x04,sf); + segment_samples[i] = read_s32be(segment_list_offset + segment_list_entry_size*i + 0x08,sf); + segment_rates[i] = read_s32be(segment_list_offset + segment_list_entry_size*i + 0x0c,sf); + + /* segments > 0 can have 0 sample rate, seems to indicate same as first + * [Ryu ga Gotoku: Kenzan! (PS3) tenkei_sng1.aix] */ + if (i > 0 && segment_rates[i] == 0) + segment_rates[i] = segment_rates[0]; /* all segments must have equal sample rate */ - if (read_32bitBE(segment_list_offset + segment_list_entry_size*i + 0x0c,streamFile) != sample_rate) { - /* segments > 0 can have 0 sample rate (Ryu ga gotoku: Kenzan! tenkei_sng1.aix), - seems to indicate same sample rate as first */ - if (!(i > 0 && read_32bitBE(segment_list_offset + segment_list_entry_size*i + 0x0c,streamFile) == 0)) - goto fail; - } + if (segment_rates[i] != segment_rates[0]) + goto fail; } - if (segment_offset[0] != data_offset) + if (segment_offsets[0] != data_offset) goto fail; } - /* base layer header */ - layer_count = (uint8_t)read_8bit(layer_list_offset,streamFile); - if (layer_count < 1) goto fail; - - layer_list_end = layer_list_offset + 0x08 + layer_count*layer_list_entry_size; - if (layer_list_end >= data_offset) goto fail; + /* between the segment and layer table some kind of 0x10 subtable? */ + if (read_u8(subtable_offset,sf) != 0x01) + goto fail; /* parse layers table */ - channel_count = 0; - for (i = 0; i < layer_count; i++) { - /* all streams must have same samplerate as segments */ - if (read_32bitBE(layer_list_offset + 0x08 + i*layer_list_entry_size + 0x00,streamFile) != sample_rate) - goto fail; - channel_count += read_8bit(layer_list_offset + 0x08 + i*layer_list_entry_size + 0x04,streamFile); - } + { + const size_t layer_list_entry_size = 0x08; + off_t layer_list_offset, layer_list_end; - /* check for existence of segments */ - for (i = 0; i < segment_count; i++) { - off_t aixp_offset = segment_offset[i]; - for (j = 0; j < layer_count; j++) { - if (read_32bitBE(aixp_offset,streamFile) != 0x41495850) /* "AIXP" */ + layer_list_offset = subtable_offset + 0x10; + if (layer_list_offset >= data_offset) goto fail; + + layer_count = read_u8(layer_list_offset,sf); + if (layer_count < 1) goto fail; + + layer_list_end = layer_list_offset + 0x08 + layer_count*layer_list_entry_size; + if (layer_list_end >= data_offset) goto fail; + + for (i = 0; i < layer_count; i++) { + /* all layers must have same sample rate as segments */ + if (read_s32be(layer_list_offset + 0x08 + i*layer_list_entry_size + 0x00,sf) != segment_rates[0]) goto fail; - if (read_8bit(aixp_offset+0x08,streamFile) != j) - goto fail; - aixp_offset += read_32bitBE(aixp_offset+0x04,streamFile) + 0x08; + /* 0x04: layer channels */ } } - /* open base streamfile, that will be shared by all open_aix_with_STREAMFILE */ - { - char filename[PATH_LIMIT]; - streamFile->get_name(streamFile,filename,sizeof(filename)); - streamFileAIX = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); //todo simplify - if (!streamFileAIX) goto fail; - } - - /* init layout */ - { - data = malloc(sizeof(aix_codec_data)); - if (!data) goto fail; - - data->segment_count = segment_count; - data->stream_count = layer_count; - data->adxs = calloc(segment_count * layer_count, sizeof(VGMSTREAM*)); - if (!data->adxs) goto fail; - - data->sample_counts = calloc(segment_count,sizeof(int32_t)); - if (!data->sample_counts) goto fail; - - memcpy(data->sample_counts,segment_samples,segment_count*sizeof(int32_t)); - } - - /* open each segment / layer subfile */ - for (i = 0; i < segment_count; i++) { - for (j = 0; j < layer_count; j++) { - //;VGM_LOG("AIX: opening segment %d/%d stream %d/%d %x\n",i,segment_count,j,stream_count,segment_offset[i]); - VGMSTREAM *temp_vgmstream; - STREAMFILE * temp_streamFile = open_aix_with_STREAMFILE(streamFileAIX,segment_offset[i],j); - if (!temp_streamFile) goto fail; - - temp_vgmstream = data->adxs[i*layer_count+j] = init_vgmstream_adx(temp_streamFile); - - close_streamfile(temp_streamFile); - - if (!temp_vgmstream) goto fail; - - /* setup layers */ - if (temp_vgmstream->num_samples != data->sample_counts[i] || temp_vgmstream->loop_flag != 0) - goto fail; - memcpy(temp_vgmstream->start_ch,temp_vgmstream->ch,sizeof(VGMSTREAMCHANNEL)*temp_vgmstream->channels); - memcpy(temp_vgmstream->start_vgmstream,temp_vgmstream,sizeof(VGMSTREAM)); - } - } - - /* get looping and samples */ - sample_count = 0; - loop_flag = (segment_count > 1); - for (i = 0; i < segment_count; i++) { - sample_count += data->sample_counts[i]; - - if (i == 0) - loop_start_sample = sample_count; - if (i == 1) - loop_end_sample = sample_count; - } - - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); + /* build combo layers + segments VGMSTREAM */ + vgmstream = build_segmented_vgmstream(sf, segment_offsets, segment_sizes, segment_samples, segment_count, layer_count); if (!vgmstream) goto fail; - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = sample_count; - vgmstream->loop_start_sample = loop_start_sample; - vgmstream->loop_end_sample = loop_end_sample; - vgmstream->meta_type = meta_AIX; - vgmstream->coding_type = data->adxs[0]->coding_type; - vgmstream->layout_type = layout_aix; - - vgmstream->ch[0].streamfile = streamFileAIX; - data->current_segment = 0; - - vgmstream->codec_data = data; - free(segment_offset); - free(segment_samples); return vgmstream; fail: - close_streamfile(streamFileAIX); close_vgmstream(vgmstream); - free(segment_samples); - free(segment_offset); - - /* free aix layout */ - if (data) { - if (data->adxs) { - int i; - for (i = 0; i < data->segment_count*data->stream_count; i++) { - close_vgmstream(data->adxs[i]); - } - free(data->adxs); - } - if (data->sample_counts) { - free(data->sample_counts); - } - free(data); - } + return NULL; +} + +static VGMSTREAM *build_layered_vgmstream(STREAMFILE *streamFile, off_t segment_offset, size_t segment_size, int layer_count) { + VGMSTREAM *vgmstream = NULL; + layered_layout_data* data = NULL; + int i; + STREAMFILE* temp_streamFile = NULL; + + + /* build layers */ + data = init_layout_layered(layer_count); + if (!data) goto fail; + + for (i = 0; i < layer_count; i++) { + /* build the layer STREAMFILE */ + temp_streamFile = setup_aix_streamfile(streamFile, segment_offset, segment_size, i, "adx"); + if (!temp_streamFile) goto fail; + + /* build the sub-VGMSTREAM */ + data->layers[i] = init_vgmstream_adx(temp_streamFile); + if (!data->layers[i]) goto fail; + + data->layers[i]->stream_size = get_streamfile_size(temp_streamFile); + + close_streamfile(temp_streamFile); + temp_streamFile = NULL; + } + + if (!setup_layout_layered(data)) + goto fail; + + + /* build the layered VGMSTREAM */ + vgmstream = allocate_layered_vgmstream(data); + if (!vgmstream) goto fail; + + return vgmstream; + +fail: + if (!vgmstream) free_layout_layered(data); + close_vgmstream(vgmstream); + close_streamfile(temp_streamFile); + return NULL; +} + +static VGMSTREAM *build_segmented_vgmstream(STREAMFILE *streamFile, off_t *segment_offsets, size_t *segment_sizes, int32_t *segment_samples, int segment_count, int layer_count) { + VGMSTREAM *vgmstream = NULL; + segmented_layout_data *data = NULL; + int i, loop_flag, loop_start_segment, loop_end_segment; + + + /* build segments */ + data = init_layout_segmented(segment_count); + if (!data) goto fail; + + for (i = 0; i < segment_count; i++) { + /* build the layered sub-VGMSTREAM */ + data->segments[i] = build_layered_vgmstream(streamFile, segment_offsets[i], segment_sizes[i], layer_count); + if (!data->segments[i]) goto fail; + + data->segments[i]->num_samples = segment_samples[i]; /* just in case */ + + data->segments[i]->stream_size = segment_sizes[i]; + } + + if (!setup_layout_segmented(data)) + goto fail; + + /* known loop cases: + * - 1 segment: main/no loop [Hatsune Miku: Project Diva (PSP)] + * - 2 segments: intro + loop [SoulCalibur IV (PS3)] + * - 3 segments: intro + loop + end [Dragon Ball Z: Burst Limit (PS3), Metroid: Other M (Wii)] + * - 4/5 segments: intros + loop + ends [Danball Senki (PSP)] + * - 39 segments: no loops but multiple segments for dynamic parts? [Tetris Collection (PS2)] */ + loop_flag = (segment_count > 0 && segment_count <= 5); + loop_start_segment = (segment_count > 3) ? 2 : 1; + loop_end_segment = (segment_count > 3) ? (segment_count - 2) : 1; + + /* build the segmented VGMSTREAM */ + vgmstream = allocate_segmented_vgmstream(data, loop_flag, loop_start_segment, loop_end_segment); + if (!vgmstream) goto fail; + + return vgmstream; + +fail: + if (!vgmstream) free_layout_segmented(data); + close_vgmstream(vgmstream); return NULL; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/aix_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/aix_streamfile.h index ae2be5984..e60537a87 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/aix_streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/aix_streamfile.h @@ -2,163 +2,150 @@ #define _AIX_STREAMFILE_H_ #include "../streamfile.h" -/* a streamfile representing a subfile inside another, in blocked AIX format */ -typedef struct _AIXSTREAMFILE { - STREAMFILE sf; - STREAMFILE *real_file; - off_t start_physical_offset; - off_t current_physical_offset; - off_t current_logical_offset; - off_t current_block_size; - int stream_id; -} AIXSTREAMFILE; +typedef struct { + /* config */ + off_t stream_offset; + size_t stream_size; + int layer_number; + + /* state */ + off_t logical_offset; /* fake offset */ + off_t physical_offset; /* actual offset */ + size_t block_size; /* current size */ + size_t skip_size; /* size from block start to reach data */ + size_t data_size; /* usable size in a block */ + + size_t logical_size; +} aix_io_data; -/*static*/ STREAMFILE *open_aix_with_STREAMFILE(STREAMFILE *file, off_t start_offset, int stream_id); +static size_t aix_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, aix_io_data* data) { + size_t total_read = 0; -static size_t read_aix(AIXSTREAMFILE *streamfile,uint8_t *dest,off_t offset,size_t length) { - size_t sz = 0; + /* re-start when previous offset (can't map logical<>physical offsets) */ + if (data->logical_offset < 0 || offset < data->logical_offset) { + data->physical_offset = data->stream_offset; + data->logical_offset = 0x00; + data->data_size = 0; + } - /*printf("trying to read %x bytes from %x (str%d)\n",length,offset,streamfile->stream_id);*/ + /* read blocks */ while (length > 0) { - int read_something = 0; - /* read the beginning of the requested block, if we can */ - if (offset >= streamfile->current_logical_offset) { - off_t to_read; - off_t length_available; + /* ignore EOF */ + if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { + break; + } - length_available = (streamfile->current_logical_offset + streamfile->current_block_size) - offset; + /* process new block */ + if (data->data_size == 0) { + uint32_t block_id = read_u32be(data->physical_offset+0x00, streamfile); + data->block_size = read_u32be(data->physical_offset+0x04, streamfile) + 0x08; - if (length < length_available) { - to_read = length; - } - else { - to_read = length_available; + /* check valid block "AIXP" id, knowing that AIX segments end with "AIXE" block too */ + if (block_id != 0x41495850 || data->block_size == 0 || data->block_size == 0xFFFFFFFF) { + break; } - if (to_read > 0) { - size_t bytes_read; + /* read target layer, otherwise skip to next block and try again */ + if (read_s8(data->physical_offset+0x08, streamfile) == data->layer_number) { + /* 0x09(1): layer count */ + data->data_size = read_s16be(data->physical_offset+0x0a, streamfile); + /* 0x0c: -1 */ + data->skip_size = 0x10; + } - bytes_read = read_streamfile(dest, - streamfile->current_physical_offset+0x10 + (offset-streamfile->current_logical_offset), - to_read,streamfile->real_file); - - sz += bytes_read; - if (bytes_read != to_read) { - return sz; /* an error which we will not attempt to handle here */ - } - - read_something = 1; - - dest += bytes_read; - offset += bytes_read; - length -= bytes_read; + /* strange AIX in Tetris Collection (PS2) with padding before ADX start (no known flag) */ + if (data->logical_offset == 0x00 && + read_u32be(data->physical_offset + 0x10, streamfile) == 0 && + read_u16be(data->physical_offset + data->block_size - 0x28, streamfile) == 0x8000) { + data->data_size = 0x28; + data->skip_size = data->block_size - 0x28; } } - if (!read_something) { - /* couldn't read anything, must seek */ - int found_block = 0; + /* move to next block */ + if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size; + data->data_size = 0; + continue; + } - /* as we have no memory we must start seeking from the beginning */ - if (offset < streamfile->current_logical_offset) { - streamfile->current_logical_offset = 0; - streamfile->current_block_size = 0; - streamfile->current_physical_offset = streamfile->start_physical_offset; + /* read data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ } + } + } - /* seek ye forwards */ - while (!found_block) { - /*printf("seek looks at %x\n",streamfile->current_physical_offset);*/ - switch (read_32bitBE(streamfile->current_physical_offset, streamfile->real_file)) { - case 0x41495850: /* AIXP */ - if (read_8bit(streamfile->current_physical_offset+8, streamfile->real_file) == streamfile->stream_id) { - streamfile->current_block_size = (uint16_t)read_16bitBE(streamfile->current_physical_offset+0x0a, streamfile->real_file); - - if (offset >= streamfile->current_logical_offset+ streamfile->current_block_size) { - streamfile->current_logical_offset += streamfile->current_block_size; - } - else { - found_block = 1; - } - } - - if (!found_block) { - streamfile->current_physical_offset += read_32bitBE(streamfile->current_physical_offset+0x04, streamfile->real_file) + 8; - } - - break; - case 0x41495846: /* AIXF */ - /* shouldn't ever see this */ - case 0x41495845: /* AIXE */ - /* shouldn't have reached the end o' the line... */ - default: - return sz; - break; - } /* end block/chunk type select */ - } /* end while !found_block */ - } /* end if !read_something */ - } /* end while length > 0 */ - - return sz; + return total_read; } -static void close_aix(AIXSTREAMFILE *streamfile) { - free(streamfile); - return; +static size_t aix_io_size(STREAMFILE *streamfile, aix_io_data* data) { + uint8_t buf[1]; + + if (data->logical_size) + return data->logical_size; + + /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ + aix_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; } -static size_t get_size_aix(AIXSTREAMFILE *streamfile) { - return 0; -} +/* Handles deinterleaving of AIX blocked layer streams */ +static STREAMFILE* setup_aix_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, const char* extension) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + aix_io_data io_data = {0}; + size_t io_data_size = sizeof(aix_io_data); -static size_t get_offset_aix(AIXSTREAMFILE *streamfile) { - return streamfile->current_logical_offset; -} + io_data.stream_offset = stream_offset; + io_data.stream_size = stream_size; + io_data.layer_number = layer_number; + io_data.logical_offset = -1; /* force reset */ -static void get_name_aix(AIXSTREAMFILE *streamfile,char *buffer,size_t length) { - strncpy(buffer,"ARBITRARY.ADX",length); - buffer[length-1]='\0'; -} + /* setup subfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; -static STREAMFILE *open_aix_impl(AIXSTREAMFILE *streamfile,const char * const filename,size_t buffersize) { - AIXSTREAMFILE *newfile; - if (strcmp(filename,"ARBITRARY.ADX")) - return NULL; + new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, aix_io_read,aix_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; - newfile = malloc(sizeof(AIXSTREAMFILE)); - if (!newfile) - return NULL; - memcpy(newfile,streamfile,sizeof(AIXSTREAMFILE)); - return &newfile->sf; -} + new_streamFile = open_buffer_streamfile(new_streamFile,0); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; -/*static*/ STREAMFILE *open_aix_with_STREAMFILE(STREAMFILE *file, off_t start_offset, int stream_id) { - AIXSTREAMFILE *streamfile = malloc(sizeof(AIXSTREAMFILE)); + if (extension) { + new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,extension); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + } - if (!streamfile) - return NULL; + return temp_streamFile; - /* success, set our pointers */ - - streamfile->sf.read = (void*)read_aix; - streamfile->sf.get_size = (void*)get_size_aix; - streamfile->sf.get_offset = (void*)get_offset_aix; - streamfile->sf.get_name = (void*)get_name_aix; - streamfile->sf.open = (void*)open_aix_impl; - streamfile->sf.close = (void*)close_aix; - - streamfile->real_file = file; - streamfile->current_physical_offset = start_offset; - streamfile->start_physical_offset = start_offset; - streamfile->current_logical_offset = 0; - streamfile->current_block_size = 0; - streamfile->stream_id = stream_id; - - return &streamfile->sf; +fail: + close_streamfile(temp_streamFile); + return NULL; } #endif /* _AIX_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/akb.c b/Frameworks/vgmstream/vgmstream/src/meta/akb.c index 91bedb819..ab19920d8 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/akb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/akb.c @@ -192,7 +192,7 @@ VGMSTREAM * init_vgmstream_akb2(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset, material_offset, extradata_offset; size_t material_size, extradata_size, stream_size; - int loop_flag = 0, channel_count, encryption_flag, codec, sample_rate, /*num_samples,*/ loop_start, loop_end; + int loop_flag = 0, channel_count, encryption_flag, codec, sample_rate, num_samples, loop_start, loop_end; int total_subsongs, target_subsong = streamFile->stream_index; /* check extensions */ @@ -237,7 +237,7 @@ VGMSTREAM * init_vgmstream_akb2(STREAMFILE *streamFile) { material_size = read_16bitLE(material_offset+0x04,streamFile); sample_rate = (uint16_t)read_16bitLE(material_offset+0x06,streamFile); stream_size = read_32bitLE(material_offset+0x08,streamFile); - //num_samples = read_32bitLE(material_offset+0x0c,streamFile); + num_samples = read_32bitLE(material_offset+0x0c,streamFile); loop_start = read_32bitLE(material_offset+0x10,streamFile); loop_end = read_32bitLE(material_offset+0x14,streamFile); @@ -263,6 +263,17 @@ VGMSTREAM * init_vgmstream_akb2(STREAMFILE *streamFile) { vgmstream->meta_type = meta_AKB; switch (codec) { + case 0x01: /* PCM16LE [Mobius: Final Fantasy (Android)] */ + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + + vgmstream->num_samples = num_samples; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + break; + + case 0x02: { /* MSADPCM [The Irregular at Magic High School Lost Zero (Android)] */ vgmstream->coding_type = coding_MSADPCM; vgmstream->layout_type = layout_none; @@ -323,7 +334,6 @@ VGMSTREAM * init_vgmstream_akb2(STREAMFILE *streamFile) { } #endif - case 0x01: /* PCM16LE */ default: goto fail; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/awc.c b/Frameworks/vgmstream/vgmstream/src/meta/awc.c index 1ff2d1eb2..bde1a21df 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/awc.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/awc.c @@ -299,7 +299,8 @@ static int parse_awc_header(STREAMFILE* streamFile, awc_header* awc) { if ((awc->num_samples && !(awc->num_samples >= num_samples - 10 && awc->num_samples <= num_samples + 10)) || (awc->sample_rate && awc->sample_rate != sample_rate) || (awc->codec && awc->codec != codec)) { - VGM_LOG("AWC: found header diffs between channels\n"); + VGM_LOG("AWC: found header diffs in channel %i, ns=%i vs %i, sr=%i vs %i, c=%i vs %i\n", + ch, awc->num_samples, num_samples, awc->sample_rate, sample_rate, awc->codec, codec); goto fail; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/baf.c b/Frameworks/vgmstream/vgmstream/src/meta/baf.c index 80308a4f8..6798fabc6 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/baf.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/baf.c @@ -7,7 +7,7 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset, header_offset, name_offset; size_t stream_size; - int loop_flag, channel_count, sample_rate, version, codec; + int loop_flag, channel_count, sample_rate, version, codec, tracks; int total_subsongs, target_subsong = streamFile->stream_index; int32_t (*read_32bit)(off_t,STREAMFILE*); @@ -27,7 +27,7 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) { /* 0x04: bank size */ version = read_32bit(0x08,streamFile); - if (version != 0x03 && version != 0x04) + if (version != 0x03 && version != 0x04 && version != 0x05) goto fail; total_subsongs = read_32bit(0x0c,streamFile); if (target_subsong == 0) target_subsong = 1; @@ -35,7 +35,7 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) { /* - in v3 */ /* 0x10: 0? */ /* 0x11: bank name */ - /* - in v4 */ + /* - in v4/5 */ /* 0x10: 1? */ /* 0x11: padding flag? */ /* 0x12: bank name */ @@ -49,6 +49,11 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) { if (i+1 == target_subsong) break; offset += read_32bit(offset+0x04, streamFile); /* WAVE size, variable per codec */ + + /* skip companion "CUE " (found in 007: Blood Stone, contains segment cues) */ + if (read_32bitBE(offset+0x00, streamFile) == 0x43554520) { + offset += read_32bit(offset+0x04, streamFile); /* CUE size */ + } } header_offset = offset; } @@ -60,38 +65,69 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) { name_offset = header_offset + 0x0c; start_offset = read_32bit(header_offset+0x2c, streamFile); stream_size = read_32bit(header_offset+0x30, streamFile); + tracks = 0; + switch(codec) { case 0x03: switch(version) { case 0x03: /* Geometry Wars (PC) */ - sample_rate = read_32bit(header_offset + 0x38, streamFile); - channel_count = read_32bit(header_offset + 0x40, streamFile); + sample_rate = read_32bit(header_offset+0x38, streamFile); + channel_count = read_32bit(header_offset+0x40, streamFile); /* no actual flag, just loop +15sec songs */ loop_flag = (pcm_bytes_to_samples(stream_size, channel_count, 16) > 15*sample_rate); break; case 0x04: /* Project Gotham Racing 4 (X360) */ - sample_rate = read_32bit(header_offset + 0x3c, streamFile); - channel_count = read_32bit(header_offset + 0x44, streamFile); - loop_flag = read_8bit(header_offset+0x4b, streamFile); + sample_rate = read_32bit(header_offset+0x3c, streamFile); + channel_count = read_32bit(header_offset+0x44, streamFile); + loop_flag = read_8bit(header_offset+0x4b, streamFile); break; + + default: + goto fail; } break; - case 0x07: /* Blur (PS3) */ - sample_rate = read_32bit(header_offset+0x40, streamFile); - loop_flag = read_8bit(header_offset+0x48, streamFile); - channel_count = read_8bit(header_offset+0x4b, streamFile); + case 0x07: + switch(version) { + case 0x04: /* Blur (PS3) */ + case 0x05: /* James Bond 007: Blood Stone (X360) */ + sample_rate = read_32bit(header_offset+0x40, streamFile); + loop_flag = read_8bit(header_offset+0x48, streamFile); + tracks = read_8bit(header_offset+0x49, streamFile); + channel_count = read_8bit(header_offset+0x4b, streamFile); + + if (tracks) { + channel_count = channel_count * tracks; + } + break; + + default: + goto fail; + } break; - case 0x08: /* Project Gotham Racing (X360) */ - sample_rate = read_32bit(header_offset+0x3c, streamFile); - channel_count = read_32bit(header_offset+0x44, streamFile); - loop_flag = read_8bit(header_offset+0x54, streamFile) != 0; + + case 0x08: + switch(version) { + case 0x04: /* Project Gotham Racing (X360) */ + sample_rate = read_32bit(header_offset+0x3c, streamFile); + channel_count = read_32bit(header_offset+0x44, streamFile); + loop_flag = read_8bit(header_offset+0x54, streamFile) != 0; + break; + + case 0x05: /* James Bond 007: Blood Stone (X360) */ + sample_rate = read_32bit(header_offset+0x40, streamFile); + channel_count = read_32bit(header_offset+0x48, streamFile); + loop_flag = read_8bit(header_offset+0x58, streamFile) != 0; + break; + + default: + goto fail; + } break; default: - VGM_LOG("BAF: unknown version %x\n", version); goto fail; } /* others: pan/vol? fixed values? (0x19, 0x10) */ @@ -129,6 +165,7 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) { vgmstream->loop_end_sample = vgmstream->num_samples; break; + #ifdef VGM_USE_FFMPEG case 0x08: { uint8_t buf[0x100]; int bytes; @@ -162,6 +199,7 @@ VGMSTREAM * init_vgmstream_baf(STREAMFILE *streamFile) { xma_fix_raw_samples_ch(vgmstream, streamFile, start_offset, stream_size, channel_count, 1,1); break; } + #endif default: VGM_LOG("BAF: unknown codec %x\n", codec); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/bgw.c b/Frameworks/vgmstream/vgmstream/src/meta/bgw.c index 4ba1acbff..98eaf6609 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/bgw.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/bgw.c @@ -114,6 +114,7 @@ fail: /* SPW (SEWave) - from PlayOnline viewer for Final Fantasy XI (PC) */ VGMSTREAM * init_vgmstream_spw(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; uint32_t codec, file_size, block_size, sample_rate, block_align; int32_t loop_start; off_t start_offset; @@ -139,8 +140,8 @@ VGMSTREAM * init_vgmstream_spw(STREAMFILE *streamFile) { /*0x2c: unk (0x00?) */ /*0x2d: unk (0x00/01?) */ channel_count = read_8bit(0x2a,streamFile); - block_align = read_8bit(0x2b,streamFile); - /*0x2c: unk (0x01 when PCM, 0x10 when VAG?) */ + /*0x2b: unk (0x01 when PCM, 0x10 when VAG?) */ + block_align = read_8bit(0x2c,streamFile); if (file_size != get_streamfile_size(streamFile)) goto fail; @@ -181,6 +182,38 @@ VGMSTREAM * init_vgmstream_spw(STREAMFILE *streamFile) { break; +#ifdef VGM_USE_FFMPEG + case 3: { /* ATRAC3 (encrypted) */ + uint8_t buf[0x100]; + int bytes, joint_stereo, skip_samples; + size_t data_size = file_size - start_offset; + + vgmstream->num_samples = block_size; /* atrac3_bytes_to_samples gives the same value */ + if (loop_flag) { + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = vgmstream->num_samples; + } + + block_align = 0xC0 * vgmstream->channels; /* 0x00 in header */ + joint_stereo = 0; + skip_samples = 0; + + bytes = ffmpeg_make_riff_atrac3(buf, 0x100, vgmstream->num_samples, data_size, vgmstream->channels, vgmstream->sample_rate, block_align, joint_stereo, skip_samples); + if (bytes <= 0) goto fail; + + temp_streamFile = setup_bgw_atrac3_streamfile(streamFile, start_offset,data_size, 0xC0,channel_count); + if (!temp_streamFile) goto fail; + + vgmstream->codec_data = init_ffmpeg_header_offset(temp_streamFile, buf,bytes, 0,data_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + close_streamfile(temp_streamFile); + break; + } +#endif + default: goto fail; } @@ -193,6 +226,7 @@ VGMSTREAM * init_vgmstream_spw(STREAMFILE *streamFile) { return vgmstream; fail: + close_streamfile(temp_streamFile); close_vgmstream(vgmstream); return NULL; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/bnk_sony.c b/Frameworks/vgmstream/vgmstream/src/meta/bnk_sony.c index b5437b691..36e343637 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/bnk_sony.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/bnk_sony.c @@ -176,8 +176,15 @@ VGMSTREAM * init_vgmstream_bnk_sony(STREAMFILE *streamFile) { case 0xC2: sample_rate = 44100; break; case 0xBC: sample_rate = 36000; break; //? case 0xBA: sample_rate = 32000; break; //? + case 0xB9: sample_rate = 30000; break; //? + case 0xB8: sample_rate = 28000; break; //? case 0xB6: sample_rate = 22050; break; + case 0xB0: sample_rate = 15000; break; //? + case 0xAF: sample_rate = 14000; break; //? + case 0xAE: sample_rate = 13000; break; //? + case 0xAC: sample_rate = 12000; break; //? case 0xAA: sample_rate = 11025; break; + case 0xA9: sample_rate = 10000; break; //? default: VGM_LOG("BNK: unknown pitch %x\n", pitch); goto fail; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/dc_dcsw_dcs.c b/Frameworks/vgmstream/vgmstream/src/meta/dc_dcsw_dcs.c deleted file mode 100644 index d8e98de67..000000000 --- a/Frameworks/vgmstream/vgmstream/src/meta/dc_dcsw_dcs.c +++ /dev/null @@ -1,119 +0,0 @@ -#include "meta.h" -#include "../util.h" - -/* WAV+DCS (DCSW+DCS) -2008-12-06 - manakoAT : Evil Twin - Cypriens Chronicles... -2008-12-07 - manakoAT : Added a function to read the Header file and for - retrieving the channels/frequency, Frequency starts - always at a "data" chunk - 0x0C bytes, Channels - always - 0x0E bytes... -2010-01-13 - manakoAT : Changed the 'Helper' extension from .wav to .dcws, to prevent conflicts */ - -VGMSTREAM * init_vgmstream_dc_dcsw_dcs(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE * streamFileDCSW = NULL; - char filename[PATH_LIMIT]; - char filenameDCSW[PATH_LIMIT]; - int i; - int channel_count; - int loop_flag; - int frequency; - int dataBuffer = 0; - int Founddata = 0; - size_t file_size; - off_t current_chunk; - - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("dcs",filename_extension(filename))) goto fail; - - /* Getting the Header file name... */ - strcpy(filenameDCSW,filename); - strcpy(filenameDCSW+strlen(filenameDCSW)-3,"dcsw"); - - /* Look if the Header file is present, else cancel vgmstream */ - streamFileDCSW = streamFile->open(streamFile,filenameDCSW,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!streamFileDCSW) goto fail; - - /* check header */ - if (read_32bitBE(0x00,streamFileDCSW) != 0x52494646 || /* "RIFF" */ - read_32bitBE(0x08,streamFileDCSW) != 0x57415645 || /* "WAVE" */ - read_32bitBE(0x0C,streamFileDCSW) != 0x34582E76 || /* 0x34582E76 */ - read_32bitBE(0x3C,streamFileDCSW) != 0x406E616D) /* "@nam" */ - goto fail; - - /* scan file until we find a "data" string */ - file_size = get_streamfile_size(streamFileDCSW); - { - current_chunk = 0; - /* Start at 0 and loop until we reached the - file size, or until we found a "data string */ - while (!Founddata && current_chunk < file_size) { - dataBuffer = (read_32bitBE(current_chunk,streamFileDCSW)); - if (dataBuffer == 0x64617461) { /* "data" */ - /* if "data" string found, retrieve the needed infos */ - Founddata = 1; - /* We will cancel the search here if we have a match */ - break; - } - /* else we will increase the search offset by 1 */ - current_chunk = current_chunk + 1; - } - } - - if (Founddata == 0) { - goto fail; - } else if (Founddata == 1) { - channel_count = (uint16_t)read_16bitLE(current_chunk-0x0E,streamFileDCSW); - frequency = read_32bitLE(current_chunk-0x0C,streamFileDCSW); - } - - loop_flag = 0; - - /* Seems we're dealing with a vaild file+header, - now we can finally build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - /* fill in the vital statistics */ - vgmstream->channels = channel_count; - vgmstream->sample_rate = frequency; - vgmstream->num_samples=(get_streamfile_size(streamFile))*2/channel_count; - - if(loop_flag) { - vgmstream->loop_start_sample = 0; - vgmstream->loop_end_sample = (get_streamfile_size(streamFile))*2/channel_count; - } - - if (channel_count == 1) { - vgmstream->layout_type = layout_none; - } else if (channel_count > 1) { - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x4000; - } - - vgmstream->coding_type = coding_AICA_int; - vgmstream->meta_type = meta_DC_DCSW_DCS; - - /* open the file for reading by each channel */ - { - for (i=0;ich[i].streamfile = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - - if (!vgmstream->ch[i].streamfile) goto fail; - vgmstream->ch[i].channel_start_offset= - vgmstream->ch[i].offset=i*vgmstream->interleave_block_size; - vgmstream->ch[i].adpcm_step_index = 0x7f; /* AICA */ - } - } - - close_streamfile(streamFileDCSW); streamFileDCSW=NULL; - - return vgmstream; - - /* clean up anything we may have opened */ -fail: - if (streamFileDCSW) close_streamfile(streamFileDCSW); - if (vgmstream) close_vgmstream(vgmstream); - return NULL; -} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/dc_str.c b/Frameworks/vgmstream/vgmstream/src/meta/dc_str.c index d9be078db..709f5204a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/dc_str.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/dc_str.c @@ -39,7 +39,7 @@ VGMSTREAM * init_vgmstream_dc_str(STREAMFILE *streamFile) { /* fill in the vital statistics */ switch (samples) { case 4: - vgmstream->coding_type = coding_AICA_int; + vgmstream->coding_type = coding_YAMAHA_int; vgmstream->num_samples = read_32bitLE(0x14,streamFile); if (loop_flag) { vgmstream->loop_start_sample = 0; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/dcs_wav.c b/Frameworks/vgmstream/vgmstream/src/meta/dcs_wav.c new file mode 100644 index 000000000..871c6fe75 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/dcs_wav.c @@ -0,0 +1,58 @@ +#include "meta.h" +#include "../coding/coding.h" + + +/* DCS+WAV - from In Utero games [Evil Twin: Cyprien's Chronicles (DC)] */ +VGMSTREAM * init_vgmstream_dcs_wav(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE * streamHeader = NULL; + int loop_flag, channel_count, sample_rate; + off_t start_offset, fmt_offset; + + + /* checks */ + if (!check_extensions(streamFile,"dcs")) + goto fail; + + streamHeader = open_streamfile_by_ext(streamFile, "wav"); + if (!streamHeader) goto fail; + + /* a slightly funny RIFF */ + if (read_u32be(0x00,streamHeader) != 0x52494646 || /* "RIFF" */ + read_u32be(0x08,streamHeader) != 0x57415645 || /* "WAVE" */ + read_u32be(0x0C,streamHeader) != 0x34582E76 || /* "4X.v" */ + read_u32be(0x3C,streamHeader) != 0x406E616D) /* "@nam" */ + goto fail; + + fmt_offset = 0x44 + read_32bitLE(0x40,streamHeader); /* skip @nam */ + if (fmt_offset % 2) fmt_offset += 1; + if (read_u32be(fmt_offset,streamHeader) != 0x666D7420) goto fail; /* "fmt " */ + fmt_offset += 0x04+0x04; + + if (read_u16le(fmt_offset+0x00,streamHeader) != 0x0005) goto fail; /* unofficial format */ + channel_count = read_u16le(fmt_offset+0x02,streamHeader); + sample_rate = read_u32le(fmt_offset+0x04,streamHeader); + loop_flag = 0; + start_offset = 0x00; + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_DCS_WAV; + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = yamaha_bytes_to_samples(get_streamfile_size(streamFile), channel_count); + vgmstream->coding_type = coding_YAMAHA_int; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x4000; + + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; + close_streamfile(streamHeader); + return vgmstream; + +fail: + close_streamfile(streamHeader); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c index 8507dcb56..9acdef8c0 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_eaac.c @@ -36,7 +36,7 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, STREAMFILE * streamData, off_t header_offset, off_t start_offset, meta_t meta_type); static size_t get_snr_size(STREAMFILE *streamFile, off_t offset); static VGMSTREAM *parse_s10a_header(STREAMFILE *streamFile, off_t offset, uint16_t target_index, off_t ast_offset); - +VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset); /* .SNR+SNS - from EA latest games (~2008-2013), v0 header */ @@ -314,7 +314,7 @@ VGMSTREAM * init_vgmstream_ea_sbr(STREAMFILE *streamFile) { uint32_t i, num_sounds, type_desc; uint16_t num_metas, meta_type; off_t table_offset, types_offset, entry_offset, metas_offset, data_offset, snr_offset, sns_offset; - STREAMFILE *sbsFile = NULL, *streamData = NULL; + STREAMFILE *sbsFile = NULL; VGMSTREAM *vgmstream = NULL; int target_stream = streamFile->stream_index; @@ -359,13 +359,30 @@ VGMSTREAM * init_vgmstream_ea_sbr(STREAMFILE *streamFile) { } } - if (snr_offset == 0xFFFFFFFF) + if (snr_offset == 0xFFFFFFFF && sns_offset == 0xFFFFFFFF) goto fail; - if (sns_offset == 0xFFFFFFFF) { + if (snr_offset == 0xFFFFFFFF) { + /* SPS file */ + sbsFile = open_streamfile_by_ext(streamFile, "sbs"); + if (!sbsFile) + goto fail; + + if (read_32bitBE(0x00, sbsFile) != 0x53424B53) /* "SBKS" */ + goto fail; + + snr_offset = sns_offset; + sns_offset = snr_offset + (read_32bitBE(snr_offset, sbsFile) & 0x00FFFFFF); + snr_offset += 0x04; + vgmstream = init_vgmstream_eaaudiocore_header(sbsFile, sbsFile, snr_offset, sns_offset, meta_EA_SNR_SNS); + if (!vgmstream) + goto fail; + } else if (sns_offset == 0xFFFFFFFF) { /* RAM asset */ - streamData = streamFile; sns_offset = snr_offset + get_snr_size(streamFile, snr_offset); + vgmstream = init_vgmstream_eaaudiocore_header(streamFile, streamFile, snr_offset, sns_offset, meta_EA_SNR_SNS); + if (!vgmstream) + goto fail; } else { /* streamed asset */ sbsFile = open_streamfile_by_ext(streamFile, "sbs"); @@ -375,13 +392,11 @@ VGMSTREAM * init_vgmstream_ea_sbr(STREAMFILE *streamFile) { if (read_32bitBE(0x00, sbsFile) != 0x53424B53) /* "SBKS" */ goto fail; - streamData = sbsFile; + vgmstream = init_vgmstream_eaaudiocore_header(streamFile, sbsFile, snr_offset, sns_offset, meta_EA_SNR_SNS); + if (!vgmstream) + goto fail; } - vgmstream = init_vgmstream_eaaudiocore_header(streamFile, streamData, snr_offset, sns_offset, meta_EA_SNR_SNS); - if (!vgmstream) - goto fail; - vgmstream->num_streams = num_sounds; close_streamfile(sbsFile); return vgmstream; @@ -569,6 +584,54 @@ fail: return NULL; } +/* EA TMX - used for engine sounds in NFS games (2007-present) */ +VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE *streamFile) { + uint32_t num_sounds, sound_type; + off_t table_offset, data_offset, entry_offset, sound_offset, sns_offset; + VGMSTREAM *vgmstream = NULL; + int target_stream = streamFile->stream_index; + + if (!check_extensions(streamFile, "tmx")) + goto fail; + + /* always little endian */ + if (read_32bitLE(0x0c, streamFile) != 0x30303031) /* "0001" */ + goto fail; + + num_sounds = read_32bitLE(0x20, streamFile); + table_offset = read_32bitLE(0x58, streamFile); + data_offset = read_32bitLE(0x5c, streamFile); + + if (target_stream == 0) target_stream = 1; + if (target_stream < 0 || num_sounds == 0 || target_stream > num_sounds) + goto fail; + + entry_offset = table_offset + (target_stream - 1) * 0x24; + sound_type = read_32bitLE(entry_offset + 0x00, streamFile); + sound_offset = read_32bitLE(entry_offset + 0x08, streamFile) + data_offset; + + switch (sound_type) { + case 0x47494E20: /* "GIN " */ + /* FIXME: need to get GIN size somehow */ + vgmstream = init_vgmstream_gin_header(streamFile, sound_offset); + if (!vgmstream) goto fail; + break; + case 0x534E5220: /* "SNR " */ + sns_offset = sound_offset + get_snr_size(streamFile, sound_offset); + vgmstream = init_vgmstream_eaaudiocore_header(streamFile, streamFile, sound_offset, sns_offset, meta_EA_SNR_SNS); + if (!vgmstream) goto fail; + break; + default: + goto fail; + } + + vgmstream->num_streams = num_sounds; + return vgmstream; + +fail: + return NULL; +} + /* EA Harmony Sample Bank - used in 8th gen EA Sports games */ VGMSTREAM * init_vgmstream_ea_sbr_harmony(STREAMFILE *streamFile) { uint32_t num_dsets, set_sounds, chunk_id; @@ -847,7 +910,8 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST eaac.codec == EAAC_CODEC_EALAYER3_V1 || eaac.codec == EAAC_CODEC_EALAYER3_V2_PCM || eaac.codec == EAAC_CODEC_EALAYER3_V2_SPIKE || - eaac.codec == EAAC_CODEC_EAXMA)) { + eaac.codec == EAAC_CODEC_EAXMA || + eaac.codec == EAAC_CODEC_XAS)) { VGM_LOG("EA EAAC: unknown actual looping for codec %x\n", eaac.codec); goto fail; } @@ -857,8 +921,11 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST switch(eaac.channel_config) { case 0x00: eaac.channels = 1; break; case 0x01: eaac.channels = 2; break; + case 0x02: eaac.channels = 3; break; /* rare [Battlefield 4 (X360)-EAXMA] */ case 0x03: eaac.channels = 4; break; + case 0x04: eaac.channels = 5; break; /* rare [Battlefield 4 (X360)-EAXMA] */ case 0x05: eaac.channels = 6; break; + case 0x06: eaac.channels = 7; break; /* rare [Battlefield 4 (X360)-EAXMA] */ case 0x07: eaac.channels = 8; break; case 0x0a: eaac.channels = 11; break; /* rare [Army of Two: The Devil's Cartel (PS3)-EALayer3v2P] */ default: @@ -910,8 +977,19 @@ static VGMSTREAM * init_vgmstream_eaaudiocore_header(STREAMFILE * streamHead, ST #endif case EAAC_CODEC_XAS: /* "Xas1": EA-XAS [Dead Space (PC/PS3)] */ - vgmstream->coding_type = coding_EA_XAS_V1; - vgmstream->layout_type = layout_blocked_ea_sns; + + /* special (if hacky) loop handling, see comments */ + if (eaac.loop_start > 0) { + segmented_layout_data *data = build_segmented_eaaudiocore_looping(streamData, &eaac); + if (!data) goto fail; + vgmstream->layout_data = data; + vgmstream->coding_type = data->segments[0]->coding_type; + vgmstream->layout_type = layout_segmented; + } else { + vgmstream->coding_type = coding_EA_XAS_V1; + vgmstream->layout_type = layout_blocked_ea_sns; + } + break; #ifdef VGM_USE_MPEG @@ -1054,6 +1132,9 @@ static size_t calculate_eaac_size(VGMSTREAM *vgmstream, STREAMFILE *streamFile, uint32_t total_samples; size_t stream_size, file_size; + if (streamFile == NULL) + return 0; + switch (eaac->codec) { case EAAC_CODEC_EAXMA: case EAAC_CODEC_EALAYER3_V1: @@ -1093,11 +1174,12 @@ static size_t calculate_eaac_size(VGMSTREAM *vgmstream, STREAMFILE *streamFile, * * We use the segmented layout, since the eaac_streamfile doesn't handle padding, * and the segments seem fully separate (so even skipping would probably decode wrong). */ -// todo consider better ways to handle this once more looped files for other codecs are found +// todo reorganize code for more standard init static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *streamData, eaac_header *eaac) { segmented_layout_data *data = NULL; - STREAMFILE* temp_streamFile[2] = {0}; + STREAMFILE* temp_streamFile = NULL; off_t offsets[2] = { eaac->stream_offset, eaac->loop_offset }; + off_t start_offset; int num_samples[2] = { eaac->loop_start, eaac->num_samples - eaac->loop_start}; int segment_count = 2; /* intro/loop */ int i; @@ -1122,6 +1204,8 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st temp_eaac.num_samples = num_samples[i]; temp_eaac.stream_offset = offsets[i]; + start_offset = 0x00; /* must point to the custom streamfile's beginning */ + /* layers inside segments, how trippy */ data->segments[i]->layout_data = build_layered_eaaudiocore_eaxma(streamData, &temp_eaac); if (!data->segments[i]->layout_data) goto fail; @@ -1131,6 +1215,15 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st } #endif + case EAAC_CODEC_XAS: + { + start_offset = offsets[i]; + + data->segments[i]->coding_type = coding_EA_XAS_V1; + data->segments[i]->layout_type = layout_blocked_ea_sns; + break; + } + #ifdef VGM_USE_MPEG case EAAC_CODEC_EALAYER3_V1: case EAAC_CODEC_EALAYER3_V2_PCM: @@ -1138,10 +1231,12 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st mpeg_custom_config cfg = {0}; mpeg_custom_t type = (eaac->codec == 0x05 ? MPEG_EAL31b : (eaac->codec == 0x06) ? MPEG_EAL32P : MPEG_EAL32S); - temp_streamFile[i] = setup_eaac_streamfile(streamData, eaac->version,eaac->codec,eaac->streamed,0,0, offsets[i]); - if (!temp_streamFile[i]) goto fail; + start_offset = 0x00; /* must point to the custom streamfile's beginning */ - data->segments[i]->codec_data = init_mpeg_custom(temp_streamFile[i], 0x00, &data->segments[i]->coding_type, eaac->channels, type, &cfg); + temp_streamFile = setup_eaac_streamfile(streamData, eaac->version,eaac->codec,eaac->streamed,0,0, offsets[i]); + if (!temp_streamFile) goto fail; + + data->segments[i]->codec_data = init_mpeg_custom(temp_streamFile, 0x00, &data->segments[i]->coding_type, eaac->channels, type, &cfg); if (!data->segments[i]->codec_data) goto fail; data->segments[i]->layout_type = layout_none; break; @@ -1151,10 +1246,14 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st goto fail; } - if (!vgmstream_open_stream(data->segments[i],temp_streamFile[i],0x00)) + if (!vgmstream_open_stream(data->segments[i],temp_streamFile == NULL ? streamData : temp_streamFile, start_offset)) goto fail; - data->segments[i]->stream_size = calculate_eaac_size(data->segments[i], temp_streamFile[i], eaac, 0x00); + close_streamfile(temp_streamFile); + temp_streamFile = NULL; + + //todo temp_streamFile doesn't contain EAXMA's streamfile + data->segments[i]->stream_size = calculate_eaac_size(data->segments[i], temp_streamFile, eaac, start_offset); } if (!setup_layout_segmented(data)) @@ -1162,8 +1261,7 @@ static segmented_layout_data* build_segmented_eaaudiocore_looping(STREAMFILE *st return data; fail: - for (i = 0; i < segment_count; i++) - close_streamfile(temp_streamFile[i]); + close_streamfile(temp_streamFile); free_layout_segmented(data); return NULL; } @@ -1212,6 +1310,7 @@ static layered_layout_data* build_layered_eaaudiocore_eaxma(STREAMFILE *streamDa data->layers[i]->coding_type = coding_FFmpeg; data->layers[i]->layout_type = layout_none; + data->layers[i]->stream_size = get_streamfile_size(temp_streamFile); xma_fix_raw_samples(data->layers[i], temp_streamFile, 0x00,stream_size, 0, 0,0); /* samples are ok? */ } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c b/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c index 4287e1463..303ea5d89 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ea_schl.c @@ -74,6 +74,7 @@ #define EA_BLOCKID_LOC_JA 0x00004A41 /* Japanese, older */ #define EA_BLOCKID_LOC_JP 0x00004A50 /* Japanese, newer */ #define EA_BLOCKID_LOC_PL 0x0000504C /* Polish */ +#define EA_BLOCKID_LOC_BR 0x00004252 /* Brazilian Portuguese */ #define EA_BNK_HEADER_LE 0x424E4B6C /* "BNKl" */ #define EA_BNK_HEADER_BE 0x424E4B62 /* "BNKb" */ @@ -135,18 +136,19 @@ VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile) { /* check header */ if (read_32bitBE(0x00,streamFile) != EA_BLOCKID_HEADER && /* "SCHl" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_EN) && /* "SHEN" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_FR) && /* "SHFR" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_GE) && /* "SHGE" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_DE) && /* "SHDE" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_IT) && /* "SHIT" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_SP) && /* "SHSP" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_ES) && /* "SHES" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_MX) && /* "SHMX" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_RU) && /* "SHRU" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JA) && /* "SHJA" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JP) && /* "SHJP" */ - read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_PL)) /* "SHPL" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_EN) && /* "SHEN" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_FR) && /* "SHFR" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_GE) && /* "SHGE" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_DE) && /* "SHDE" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_IT) && /* "SHIT" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_SP) && /* "SHSP" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_ES) && /* "SHES" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_MX) && /* "SHMX" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_RU) && /* "SHRU" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JA) && /* "SHJA" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_JP) && /* "SHJP" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_PL) && /* "SHPL" */ + read_32bitBE(0x00,streamFile) != (EA_BLOCKID_LOC_HEADER | EA_BLOCKID_LOC_BR)) /* "SHBR" */ goto fail; /* Stream is divided into blocks/chunks: SCHl=audio header, SCCl=count of SCDl, SCDl=data xN, SCLl=loop end, SCEl=end. @@ -158,6 +160,89 @@ fail: return NULL; } +/* EA SCHl inside non-demuxed videos, used in current gen games too */ +VGMSTREAM * init_vgmstream_ea_schl_video(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t offset = 0, start_offset = 0; + int blocks_done = 0; + int total_subsongs, target_subsong = streamFile->stream_index; + int32_t(*read_32bit)(off_t, STREAMFILE*); + + + /* check extension */ + /* .vp6: ~late */ + if (!check_extensions(streamFile,"vp6")) + goto fail; + + /* check initial movie block id */ + if (read_32bitBE(0x00,streamFile) != 0x4D566864) /* "MVhd" */ + goto fail; + + /* use block size to check endianness */ + if (guess_endianness32bit(0x04, streamFile)) { + read_32bit = read_32bitBE; + } else { + read_32bit = read_32bitLE; + } + + /* find starting valid header for the parser */ + while (offset < get_streamfile_size(streamFile)) { + uint32_t block_id = read_32bitBE(offset+0x00,streamFile); + uint32_t block_size = read_32bit (offset+0x04,streamFile); + + /* find "SCHl" or "SHxx" blocks */ + if ((block_id == EA_BLOCKID_HEADER) || ((block_id & 0xFFFF0000) == EA_BLOCKID_LOC_HEADER)) { + start_offset = offset; + break; + } + + if (block_size == 0xFFFFFFFF) + goto fail; + if (blocks_done > 10) + goto fail; /* unlikely to contain music */ + + blocks_done++; + offset += block_size; + } + + if (start_offset == 0) + goto fail; + + /* find target subsong (one per each SHxx multilang block) */ + total_subsongs = 1; + if (target_subsong == 0) target_subsong = 1; + offset = start_offset; + while (offset < get_streamfile_size(streamFile)) { + uint32_t block_id = read_32bitBE(offset+0x00,streamFile); + uint32_t block_size = read_32bit (offset+0x04,streamFile); + + /* no more subsongs (assumes all SHxx headers go together) */ + if (((block_id & 0xFFFF0000) != EA_BLOCKID_LOC_HEADER)) { + break; + } + + if (target_subsong == total_subsongs) { + start_offset = offset; + /* keep counting subsongs */ + } + + total_subsongs++; + offset += block_size; + } + + if (target_subsong < 0 || target_subsong > total_subsongs || total_subsongs < 1) goto fail; + + vgmstream = parse_schl_block(streamFile, start_offset, 1); + if (!vgmstream) goto fail; + + vgmstream->num_streams = total_subsongs; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + /* EA BNK with variable header - from EA games SFXs; also created by sx.exe */ VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE *streamFile) { off_t offset; @@ -292,7 +377,7 @@ VGMSTREAM * init_vgmstream_ea_abk(STREAMFILE *streamFile) { if (!bnk_offset) goto fail; - bnk_target_stream = read_32bit(target_entry_offset + 0x04, streamFile) + 1; + bnk_target_stream = read_32bit(target_entry_offset + 0x04, streamFile); vgmstream = parse_bnk_header(streamFile, bnk_offset, bnk_target_stream, 1); if (!vgmstream) goto fail; @@ -702,8 +787,16 @@ fail: static VGMSTREAM * parse_schl_block(STREAMFILE *streamFile, off_t offset, int standalone) { off_t start_offset, header_offset; size_t header_size; + uint32_t header_id; ea_header ea = { 0 }; + /* use higher bits to store target localized block in case of multilang video, + * so only header sub-id will be read and other langs skipped */ + header_id = read_32bitBE(offset + 0x00, streamFile); + if ((header_id & 0xFFFF0000) == EA_BLOCKID_LOC_HEADER) { + ea.codec_config |= (header_id & 0xFFFF) << 16; + } + if (guess_endianness32bit(offset + 0x04, streamFile)) { /* size is always LE, except in early SS/MAC */ header_size = read_32bitBE(offset + 0x04, streamFile); ea.codec_config |= 0x02; @@ -730,7 +823,7 @@ fail: static VGMSTREAM * parse_bnk_header(STREAMFILE *streamFile, off_t offset, int target_stream, int is_embedded) { uint32_t i; uint16_t num_sounds; - off_t header_offset, start_offset, test_offset, table_offset; + off_t header_offset, start_offset, test_offset, table_offset, entry_offset; size_t header_size; ea_header ea = { 0 }; int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; @@ -779,15 +872,17 @@ static VGMSTREAM * parse_bnk_header(STREAMFILE *streamFile, off_t offset, int ta if (target_stream < 0 || target_stream >= num_sounds) goto fail; - header_offset = read_32bit(offset + table_offset + 0x04 * target_stream, streamFile); + entry_offset = offset + table_offset + 0x04 * target_stream; + header_offset = entry_offset + read_32bit(entry_offset, streamFile); } else { /* some of these are dummies with zero offset, skip them when opening standalone BNK */ for (i = 0; i < num_sounds; i++) { - test_offset = read_32bit(offset + table_offset + 0x04 * i, streamFile); + entry_offset = offset + table_offset + 0x04 * i; + test_offset = read_32bit(entry_offset, streamFile); if (test_offset != 0) { if (target_stream == real_bnk_sounds) - header_offset = offset + table_offset + 0x04 * i + test_offset; + header_offset = entry_offset + test_offset; real_bnk_sounds++; } @@ -1497,6 +1592,7 @@ static off_t get_ea_stream_mpeg_start_offset(STREAMFILE* streamFile, off_t start size_t file_size = get_streamfile_size(streamFile); off_t block_offset = start_offset; int32_t (*read_32bit)(off_t,STREAMFILE*) = ea->big_endian ? read_32bitBE : read_32bitLE; + uint32_t header_lang = (ea->codec_config >> 16) & 0xFFFF; while (block_offset < file_size) { uint32_t block_id, block_size; @@ -1508,27 +1604,16 @@ static off_t get_ea_stream_mpeg_start_offset(STREAMFILE* streamFile, off_t start if (block_size > 0x00F00000) /* size is always LE, except in early SAT/MAC */ block_size = read_32bitBE(block_offset+0x04,streamFile); - switch(block_id) { - case EA_BLOCKID_DATA: /* "SCDl" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_EN: /* "SDEN" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_FR: /* "SDFR" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_GE: /* "SDGE" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_DE: /* "SDDE" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_IT: /* "SDIT" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_SP: /* "SDSP" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_ES: /* "SDES" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_MX: /* "SDMX" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_RU: /* "SDRU" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_JA: /* "SDJA" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_JP: /* "SDJP" */ - case EA_BLOCKID_LOC_DATA | EA_BLOCKID_LOC_PL: /* "SDPL" */ - offset = read_32bit(block_offset+0x0c,streamFile); /* first value seems ok, second is something else in EALayer3 */ - return block_offset + 0x0c + ea->channels*0x04 + offset; - case 0x00000000: - goto fail; /* just in case */ - default: - block_offset += block_size; /* size includes header */ - break; + if (block_id == EA_BLOCKID_DATA || block_id == ((EA_BLOCKID_LOC_DATA | header_lang))) { + /* "SCDl" or target "SDxx" multilang blocks */ + offset = read_32bit(block_offset+0x0c,streamFile); /* first value seems ok, second is something else in EALayer3 */ + return block_offset + 0x0c + ea->channels*0x04 + offset; + } + else if (block_id == 0x00000000) { + goto fail; /* just in case */ + } + else { + block_offset += block_size; /* size includes header */ } } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ffdl.c b/Frameworks/vgmstream/vgmstream/src/meta/ffdl.c new file mode 100644 index 000000000..b9440b618 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/ffdl.c @@ -0,0 +1,97 @@ +#include "meta.h" +#include "../coding/coding.h" + + +/* FFDL - Matrix Software wrapper [Final Fantasy Dimensions (Android/iOS)] */ +VGMSTREAM * init_vgmstream_ffdl(STREAMFILE *sf) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_sf = NULL; + int loop_flag = 0, is_ffdl = 0; + int32_t num_samples = 0, loop_start_sample = 0, loop_end_sample = 0; + off_t start_offset; + size_t file_size; + + + /* checks */ + /* .ogg/logg: probable extension for Android + * .mp4/lmp4: probable extension for iOS + * .bin: iOS FFDL extension + * (extensionless): for FFDL files without names in Android .obb bigfile */ + if (!check_extensions(sf, "ogg,logg,mp4,lmp4,bin,")) + goto fail; + + /* "FFDL" is a wrapper used in all of the game's files, that may contain standard + * Ogg/MP4 or "mtxs" w/ loops + Ogg/MP4, and may concatenate multiple of them + * (without size in sight), so they should be split externally first. */ + + start_offset = 0x00; + + /* may start with wrapper (not split) */ + if (read_u32be(0x00,sf) == 0x4646444C) { /* "FFDL" */ + is_ffdl = 1; + start_offset += 0x04; + } + + /* may start with sample info (split) or after "FFDL" */ + if (read_u32be(start_offset+0x00,sf) == 0x6D747873) { /* "mtxs" */ + is_ffdl = 1; + + num_samples = read_s32le(start_offset + 0x04,sf); + loop_start_sample = read_s32le(start_offset + 0x08,sf); + loop_end_sample = read_s32le(start_offset + 0x0c,sf); + loop_flag = !(loop_start_sample==0 && loop_end_sample==num_samples); + + start_offset += 0x10; + } + + /* don't parse regular files */ + if (!is_ffdl) + goto fail; + + file_size = get_streamfile_size(sf) - start_offset; + + if (read_u32be(start_offset + 0x00,sf) == 0x4F676753) { /* "OggS" */ +#ifdef VGM_USE_VORBIS + temp_sf = setup_subfile_streamfile(sf, start_offset, file_size, "ogg"); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_ogg_vorbis(temp_sf); + if (!vgmstream) goto fail; +#else + goto fail; +#endif + } + else if (read_u32be(start_offset + 0x04,sf) == 0x66747970) { /* "ftyp" after atom size */ +#ifdef VGM_USE_FFMPEG + temp_sf = setup_subfile_streamfile(sf, start_offset, file_size, "mp4"); + if (!temp_sf) goto fail; + + vgmstream = init_vgmstream_mp4_aac_ffmpeg(temp_sf); + if (!vgmstream) goto fail; +#else + goto fail; +#endif + } + else { + goto fail; + } + + /* install loops */ + if (loop_flag) { + /* num_samples is erratic (can be bigger = padded, or smaller = cut; doesn't matter for looping though) */ + //;VGM_ASSERT(vgmstream->num_samples != num_samples, + // "FFDL: mtxs samples = %i vs num_samples = %i\n", num_samples, vgmstream->num_samples); + //vgmstream->num_samples = num_samples; + + /* loop samples are within num_samples, and don't have encoder delay (loop_start=0 starts from encoder_delay) */ + vgmstream_force_loop(vgmstream, 1, loop_start_sample, loop_end_sample); + } + + close_streamfile(temp_sf); + return vgmstream; + +fail: + close_streamfile(temp_sf); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c b/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c index b0e9004ab..c968ae865 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ffmpeg.c @@ -19,6 +19,11 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, int32_t loop_start = 0, loop_end = 0, num_samples = 0; int total_subsongs, target_subsong = streamFile->stream_index; + /* no checks */ + //if (!check_extensions(streamFile, "...")) + // goto fail; + + /* init ffmpeg */ ffmpeg_codec_data *data = init_ffmpeg_offset(streamFile, start, size); if (!data) return NULL; @@ -41,6 +46,21 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, } } + /* hack for AAC files (will return 0 samples if not an actual file) */ + if (!num_samples && check_extensions(streamFile, "aac,laac")) { + num_samples = aac_get_samples(streamFile, 0x00, get_streamfile_size(streamFile)); + } + + /* hack for MP3 files (will return 0 samples if not an actual file) */ + if (!num_samples && check_extensions(streamFile, "mp3,lmp3")) { + num_samples = mpeg_get_samples(streamFile, 0x00, get_streamfile_size(streamFile)); + } + + /* default but often inaccurate when calculated using bitrate (wrong for VBR) */ + if (!num_samples) { + num_samples = data->totalSamples; + } + /* build VGMSTREAM */ vgmstream = allocate_vgmstream(data->channels, loop_flag); @@ -52,11 +72,7 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, vgmstream->codec_data = data; vgmstream->layout_type = layout_none; - if (!num_samples) { - num_samples = data->totalSamples; - } vgmstream->num_samples = num_samples; - if (loop_flag) { vgmstream->loop_start_sample = loop_start; vgmstream->loop_end_sample = loop_end; @@ -66,6 +82,8 @@ VGMSTREAM * init_vgmstream_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, if (vgmstream->num_samples <= 0) goto fail; + vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data); + return vgmstream; fail: diff --git a/Frameworks/vgmstream/vgmstream/src/meta/fsb5.c b/Frameworks/vgmstream/vgmstream/src/meta/fsb5.c index a9445b5d7..5be34b805 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/fsb5.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/fsb5.c @@ -44,8 +44,10 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) { int i; - /* check extension, case insensitive */ - if (!check_extensions(streamFile,"fsb")) + /* checks */ + /* .fsb: standard + * .snd: Alchemy engine (also Unity) */ + if (!check_extensions(streamFile,"fsb,snd")) goto fail; if (read_32bitBE(0x00,streamFile) != 0x46534235) /* "FSB5" */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/fsb5_fev.c b/Frameworks/vgmstream/vgmstream/src/meta/fsb5_fev.c new file mode 100644 index 000000000..ae69c0304 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/fsb5_fev.c @@ -0,0 +1,54 @@ +#include "meta.h" +#include "../coding/coding.h" + +/* FEV+FSB5 container [Just Cause 3 (PC), Shantae: Half-Genie Hero (Switch)] */ +VGMSTREAM * init_vgmstream_fsb5_fev_bank(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + off_t subfile_offset, chunk_offset, first_offset = 0x0c; + size_t subfile_size, chunk_size; + + + /* checks */ + if (!check_extensions(streamFile, "bank")) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x52494646) /* "RIFF" */ + goto fail; + if (read_32bitBE(0x08,streamFile) != 0x46455620) /* "FEV " */ + goto fail; + + /* .fev is an event format referencing various external .fsb, but FMOD can bake .fev and .fsb to + * form a .bank, which is the format we support here (regular .fev is complex and not very interesting). + * Format is RIFF with FMT (main), LIST (config) and SND (FSB5 data), we want the FSB5 offset inside LIST */ + if (!find_chunk_le(streamFile, 0x4C495354,first_offset,0, &chunk_offset,NULL)) /* "LIST" */ + goto fail; + + if (read_32bitBE(chunk_offset+0x00,streamFile) != 0x50524F4A || /* "PROJ" */ + read_32bitBE(chunk_offset+0x04,streamFile) != 0x424E4B49) /* "BNKI" */ + goto fail; /* event .fev has "OBCT" instead of "BNKI" */ + + /* inside BNKI is a bunch of LIST each with event subchunks and finally the fsb offset */ + first_offset = chunk_offset + 0x04; + if (!find_chunk_le(streamFile, 0x534E4448,first_offset,0, &chunk_offset,&chunk_size)) /* "SNDH" */ + goto fail; + + if (chunk_size != 0x0c) + goto fail; /* assuming only one FSB5 is possible */ + subfile_offset = read_32bitLE(chunk_offset+0x04,streamFile); + subfile_size = read_32bitLE(chunk_offset+0x08,streamFile); + + + temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, "fsb"); + if (!temp_streamFile) goto fail; + + vgmstream = init_vgmstream_fsb5(temp_streamFile); + close_streamfile(temp_streamFile); + + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/fsb_encrypted.c b/Frameworks/vgmstream/vgmstream/src/meta/fsb_encrypted.c index a6af18da7..69564a451 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/fsb_encrypted.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/fsb_encrypted.c @@ -10,8 +10,10 @@ static STREAMFILE* setup_fsb_streamfile(STREAMFILE *streamFile, const uint8_t * VGMSTREAM * init_vgmstream_fsb_encrypted(STREAMFILE * streamFile) { VGMSTREAM * vgmstream = NULL; - /* check extensions */ - if ( !check_extensions(streamFile, "fsb") ) + /* checks */ + /* .fsb: standard + * .fsb.xen: various Guitar Hero (X360) */ + if ( !check_extensions(streamFile, "fsb,xen") ) goto fail; /* ignore non-encrypted FSB */ @@ -150,6 +152,10 @@ static STREAMFILE* setup_fsb_streamfile(STREAMFILE *streamFile, const uint8_t * if (!new_streamFile) goto fail; temp_streamFile = new_streamFile; + new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,"fsb"); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + return temp_streamFile; fail: diff --git a/Frameworks/vgmstream/vgmstream/src/meta/fsb_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/fsb_keys.h index b871f2081..cb44125cc 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/fsb_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/fsb_keys.h @@ -9,7 +9,7 @@ typedef struct { } fsbkey_info; /** - * List of known keys, found in aluigi's site (http://aluigi.altervista.org), forums and with guessfsb.exe + * List of known keys, found in aluigi's site (http://aluigi.altervista.org), forums, guessfsb.exe or manually. */ /* DJ Hero 2 (X360) */ //"nos71RiT" @@ -72,6 +72,12 @@ static const uint8_t key_cro[] = { 0x67,0x68,0x66,0x78,0x68,0x73,0x6C,0x72,0x67, /* Monster Jam (PS2) */ //"truck/impact/carbody" static const uint8_t key_mtj[] = { 0x74,0x72,0x75,0x63,0x6B,0x2F,0x69,0x6D,0x70,0x61,0x63,0x74,0x2F,0x63,0x61,0x72,0x62,0x6F,0x64,0x79 }; +/* Guitar Hero 5 (X360) */ +static const uint8_t key_gh5[] = { 0xFC,0xF9,0xE4,0xB3,0xF5,0x57,0x5C,0xA5,0xAC,0x13,0xEC,0x4A,0x43,0x19,0x58,0xEB,0x4E,0xF3,0x84,0x0B,0x8B,0x78,0xFA,0xFD,0xBB,0x18,0x46,0x7E,0x31,0xFB,0xD0 }; + +/* 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 }; + // Unknown: // - Battle: Los Angeles // - Guitar Hero: Warriors of Rock, DJ hero FSB @@ -133,6 +139,8 @@ static const fsbkey_info fsbkey_list[] = { { 1,1, sizeof(key_sc2),key_sc2 },//untested { 1,0, sizeof(key_cro),key_cro }, { 0,1, sizeof(key_mtj),key_mtj },// FSB3 + { 0,1, sizeof(key_gh5),key_gh5 },// FSB4 + { 1,0, sizeof(key_sek),key_sek },// FSB5 }; static const int fsbkey_list_count = sizeof(fsbkey_list) / sizeof(fsbkey_list[0]); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/genh.c b/Frameworks/vgmstream/vgmstream/src/meta/genh.c index c3bdfad9f..509ab1017 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/genh.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/genh.c @@ -16,7 +16,7 @@ typedef enum { DVI_IMA = 7, /* DVI IMA ADPCM (high nibble first) */ MPEG = 8, /* MPEG (MP3) */ IMA = 9, /* IMA ADPCM (low nibble first) */ - AICA = 10, /* AICA ADPCM (Dreamcast games) */ + YAMAHA = 10, /* YAMAHA (AICA) ADPCM (Dreamcast games) */ MSADPCM = 11, /* MS ADPCM (Windows games) */ NGC_DSP = 12, /* NGC DSP (Nintendo games) */ PCM8_U_int = 13, /* 8-bit unsigned PCM (interleaved) */ @@ -33,7 +33,8 @@ typedef enum { PCFX = 24, /* PC-FX ADPCM */ PCM4 = 25, /* 4-bit signed PCM (3rd and 4th gen games) */ PCM4_U = 26, /* 4-bit unsigned PCM (3rd and 4th gen games) */ - OKI16 = 27, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */ + OKI16 = 27, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */ + AAC = 28, /* Advanced Audio Coding (raw without .mp4) */ } genh_type; typedef struct { @@ -101,7 +102,7 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) { case MPEG: coding = coding_MPEG_layer3; break; /* we later find out exactly which */ #endif case IMA: coding = coding_IMA; break; - case AICA: coding = coding_AICA; break; + case YAMAHA: coding = coding_YAMAHA; break; case MSADPCM: coding = coding_MSADPCM; break; case NGC_DSP: coding = coding_NGC_DSP; break; case PCM8_U_int: coding = coding_PCM8_U_int; break; @@ -115,6 +116,7 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) { case XMA1: case XMA2: case AC3: + case AAC: case FFMPEG: coding = coding_FFmpeg; break; #endif case PCFX: coding = coding_PCFX; break; @@ -151,7 +153,7 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) { case coding_PSX_badflags: case coding_DVI_IMA: case coding_IMA: - case coding_AICA: + case coding_YAMAHA: case coding_APPLE_IMA4: vgmstream->interleave_block_size = genh.interleave; vgmstream->interleave_last_block_size = genh.interleave_last; @@ -170,8 +172,8 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) { coding = coding_DVI_IMA_int; if (coding == coding_IMA) coding = coding_IMA_int; - if (coding == coding_AICA) - coding = coding_AICA_int; + if (coding == coding_YAMAHA) + coding = coding_YAMAHA_int; } /* to avoid endless loops */ @@ -188,10 +190,10 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) { } /* setup adpcm */ - if (coding == coding_AICA || coding == coding_AICA_int) { - int i; - for (i=0;ichannels;i++) { - vgmstream->ch[i].adpcm_step_index = 0x7f; + if (coding == coding_YAMAHA || coding == coding_YAMAHA_int) { + int ch; + for (ch = 0; ch < vgmstream->channels; ch++) { + vgmstream->ch[ch].adpcm_step_index = 0x7f; } } @@ -293,7 +295,7 @@ VGMSTREAM * init_vgmstream_genh(STREAMFILE *streamFile) { case coding_FFmpeg: { ffmpeg_codec_data *ffmpeg_data = NULL; - if (genh.codec == FFMPEG || genh.codec == AC3) { + if (genh.codec == FFMPEG || genh.codec == AC3 || genh.codec == AAC) { /* default FFmpeg */ ffmpeg_data = init_ffmpeg_offset(streamFile, genh.start_offset,genh.data_size); if ( !ffmpeg_data ) goto fail; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/gin.c b/Frameworks/vgmstream/vgmstream/src/meta/gin.c index 36953a6e5..3c5cf47a9 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/gin.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/gin.c @@ -1,18 +1,32 @@ #include "meta.h" #include "../coding/coding.h" +VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset); + /* .gin - EA engine sounds [Need for Speed: Most Wanted (multi)] */ VGMSTREAM * init_vgmstream_gin(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; - off_t start_offset; - int loop_flag, channel_count, sample_rate, num_samples; - - /* checks */ if (!check_extensions(streamFile, "gin")) goto fail; - if (read_32bitBE(0x00,streamFile) != 0x476E7375) /* "Gnsu" */ + vgmstream = init_vgmstream_gin_header(streamFile, 0x00); + if (!vgmstream) + goto fail; + + return vgmstream; + +fail: + return NULL; +} + +VGMSTREAM * init_vgmstream_gin_header(STREAMFILE *streamFile, off_t offset) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate, num_samples; + + /* checks */ + if (read_32bitBE(offset + 0x00, streamFile) != 0x476E7375) /* "Gnsu" */ goto fail; /* contains mapped values for engine RPM sounds but we'll just play the whole thing */ @@ -22,11 +36,11 @@ VGMSTREAM * init_vgmstream_gin(STREAMFILE *streamFile) { /* 0x14: RPM ??? table size */ /* always LE even on X360/PS3 */ - num_samples = read_32bitLE(0x18, streamFile); - sample_rate = read_32bitLE(0x1c, streamFile); - start_offset = 0x20 + - (read_32bitLE(0x10, streamFile) + 1) * 0x04 + - (read_32bitLE(0x14, streamFile) + 1) * 0x04; + num_samples = read_32bitLE(offset + 0x18, streamFile); + sample_rate = read_32bitLE(offset + 0x1c, streamFile); + start_offset = offset + 0x20 + + (read_32bitLE(offset + 0x10, streamFile) + 1) * 0x04 + + (read_32bitLE(offset + 0x14, streamFile) + 1) * 0x04; channel_count = 1; loop_flag = 0; @@ -40,10 +54,12 @@ VGMSTREAM * init_vgmstream_gin(STREAMFILE *streamFile) { vgmstream->num_samples = num_samples; vgmstream->coding_type = coding_EA_XAS_V0; - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x13; + vgmstream->layout_type = layout_none; - if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + /* calculate size for TMX */ + vgmstream->stream_size = (align_size_to_block(num_samples, 32) / 32) * 0x13; + + if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) goto fail; return vgmstream; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/hca.c b/Frameworks/vgmstream/vgmstream/src/meta/hca.c index 7fe5f68c9..cfcd4f89f 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/hca.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/hca.c @@ -129,4 +129,6 @@ done: VGM_ASSERT(best_score > 1, "HCA: best key=%08x%08x (score=%i)\n", (uint32_t)((*out_keycode >> 32) & 0xFFFFFFFF), (uint32_t)(*out_keycode & 0xFFFFFFFF), best_score); + + VGM_ASSERT(best_score < 0, "HCA: key not found\n"); } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h index 31abc22be..de11bcc69 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys.h @@ -258,9 +258,6 @@ static const hcakey_info hcakey_list[] = { // Onsen Musume: Yunohana Kore Kushon (Android) voices {6667}, // 0000000000001A0B - /* Dragalia Lost (Cygames) [iOS/Android] */ - {2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD - /* Libra of Precatus (Android) */ {0x6D8EFB700870FCD4}, // 6D8EFB700870FCD4 @@ -273,6 +270,17 @@ static const hcakey_info hcakey_list[] = { /* Kotodaman (Android) */ {19850716}, // 00000000012EE5DC + /* Puchiguru Love Live! (Android) */ + {355541041372}, // 00000052C7E5C0DC + + /* Dolls Order (Android) */ + {153438415134838}, // 00008B8D2A3AA076 + + /* Fantasy Life Online (Android) */ + {123456789}, // 00000000075BCD15 + + /* Dragalia Lost (Cygames) [iOS/Android] */ + {2967411924141, subkeys_dgl, sizeof(subkeys_dgl) / sizeof(subkeys_dgl[0]) }, // 000002B2E7889CAD }; #endif/*_HCA_KEYS_H_*/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys_awb.h b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys_awb.h index 031c0dd56..04e180a18 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/hca_keys_awb.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/hca_keys_awb.h @@ -4,48 +4,80 @@ /* AWB subkey tables */ static const uint16_t subkeys_dgl[] = { /* Dragalia Lost (Cygames) [iOS/Android] */ - 0x0152,0x0165,0x0192,0x01E8,0x0240,0x026B,0x02B8,0x0348,0x055E,0x0595,0x05DC,0x0606,0x0636,0x0690,0x06A1,0x06A4, - 0x06B3,0x0760,0x0767,0x076E,0x076F,0x077B,0x07A6,0x07AA,0x07B4,0x07CC,0x080D,0x082B,0x084C,0x084E,0x0859,0x085B, - 0x0861,0x0864,0x0865,0x0873,0x088A,0x089B,0x08A5,0x08A7,0x08C0,0x08C3,0x08C6,0x08D5,0x08D8,0x08E0,0x08E8,0x08EB, - 0x08EF,0x08F0,0x0902,0x0908,0x090B,0x0916,0x0919,0x0923,0x0926,0x0929,0x092C,0x092D,0x092F,0x0942,0x0944,0x094C, - 0x094E,0x0957,0x095F,0x0963,0x0964,0x096C,0x097B,0x097D,0x0988,0x099E,0x09AE,0x09C1,0x09C2,0x09C5,0x09C8,0x09D7, - 0x09FF,0x0A00,0x0A02,0x0A12,0x0A16,0x0A19,0x0A25,0x0A2A,0x0A2F,0x0A30,0x0A31,0x0A3F,0x0A41,0x0A53,0x0A63,0x0A65, - 0x0A6D,0x0A73,0x0A77,0x0A99,0x0A9A,0x0AA5,0x0AA8,0x0AB8,0x0AC2,0x0AC5,0x0ACC,0x0AE0,0x0AE3,0x0AEC,0x0B09,0x0B0C, - 0x0B35,0x0B5B,0x0B6F,0x0B7C,0x0B8D,0x0BA1,0x0BB0,0x0BBE,0x0BC9,0x0BDE,0x0BEF,0x0C04,0x0C0F,0x0C30,0x0C8B,0x0C9A, - 0x0C9F,0x0CA9,0x0CB9,0x0D71,0x0D7B,0x0DB0,0x0DCF,0x0DD4,0x0DE6,0x0E1C,0x0E66,0x0E9D,0x0EAF,0x0EBB,0x0FDA,0x102F, - 0x103A,0x1074,0x10BF,0x10C7,0x1107,0x112F,0x113A,0x116F,0x11A6,0x11FB,0x1220,0x130E,0x1335,0x135B,0x13C0,0x1474, - 0x1538,0x1585,0x15AD,0x15E1,0x1748,0x178C,0x1892,0x18A4,0x1944,0x197A,0x19F3,0x1A47,0x1A5B,0x1A89,0x1AAC,0x1BE2, - 0x1C31,0x1C3B,0x1C7C,0x1C7E,0x1C89,0x1D01,0x1D0C,0x1D5A,0x1DA3,0x1E1E,0x1E88,0x1F0A,0x1F6D,0x1FC2,0x2000,0x2007, - 0x208C,0x20E9,0x20EE,0x2120,0x2160,0x2161,0x216E,0x21CA,0x21DD,0x221E,0x2227,0x2265,0x2275,0x2277,0x22C4,0x22FA, - 0x2330,0x2379,0x23C9,0x23D8,0x2415,0x243C,0x245A,0x24AD,0x24CD,0x2514,0x2540,0x2632,0x264C,0x269B,0x26B5,0x26C5, - 0x26FC,0x2701,0x2709,0x274D,0x274E,0x2777,0x27EE,0x284B,0x2882,0x288C,0x28A9,0x28B6,0x292B,0x2930,0x29B0,0x29B2, - 0x29D9,0x2A0A,0x2A47,0x2A55,0x2A5D,0x2ADB,0x2AE7,0x2D08,0x2DB0,0x2DB7,0x2DC3,0x2E6E,0x2E75,0x2E9E,0x2EB6,0x2ED8, - 0x2F64,0x2F66,0x2F72,0x30BB,0x30CA,0x30E6,0x3160,0x3218,0x32E8,0x333F,0x338B,0x33BE,0x355A,0x357D,0x35DD,0x360C, - 0x3619,0x36AA,0x3738,0x3752,0x37DE,0x3804,0x380C,0x3920,0x39DB,0x3C7B,0x3CA3,0x3D50,0x3EB2,0x3EF2,0x3F42,0x3F79, - 0x4000,0x403D,0x40BF,0x40DE,0x413E,0x414F,0x41A3,0x42EA,0x434A,0x43D3,0x43E9,0x4411,0x4541,0x4663,0x4751,0x4793, - 0x47DF,0x4840,0x487A,0x48D7,0x49B4,0x49E3,0x4B0E,0x4B21,0x4B8D,0x4C49,0x4C72,0x4D44,0x4D86,0x4DEA,0x4DF5,0x4E40, - 0x4F60,0x4F92,0x4FF2,0x5137,0x5220,0x5493,0x54CE,0x552C,0x559B,0x5639,0x567C,0x56A1,0x56EE,0x5794,0x582E,0x5846, - 0x586A,0x58B1,0x58C3,0x59A5,0x59EF,0x5A86,0x5B19,0x5B49,0x5B5C,0x5B90,0x5BCF,0x5C38,0x5C75,0x5C83,0x5D61,0x5DE0, - 0x5E47,0x5E57,0x5ED5,0x5FAF,0x6000,0x601F,0x6071,0x6104,0x6188,0x6191,0x61B2,0x61EB,0x6226,0x627D,0x62F9,0x63DE, - 0x645F,0x64D3,0x659D,0x65D3,0x65D4,0x65F2,0x6631,0x6914,0x6990,0x6993,0x6A52,0x6A85,0x6B16,0x6BF6,0x6CD6,0x6CE3, - 0x6CF9,0x6CFA,0x6D0E,0x6D16,0x6D1E,0x6D3E,0x6D80,0x6DCF,0x6E73,0x6EC5,0x6F14,0x6FD0,0x7033,0x704C,0x7098,0x7242, - 0x7278,0x72B7,0x7319,0x7427,0x7432,0x747F,0x7484,0x749D,0x74D7,0x74D8,0x756A,0x7576,0x7588,0x75A9,0x764D,0x773C, - 0x7766,0x7895,0x78B0,0x78F3,0x7976,0x797F,0x798F,0x79AB,0x79E7,0x79F9,0x7A1D,0x7A24,0x7A47,0x7B64,0x7BBC,0x7C2E, - 0x7CBA,0x7D0D,0x7D12,0x7E66,0x7EB5,0x7EBA,0x7EC5,0x7F1B,0x7F26,0x7F5F,0x7FC1,0x7FCE,0x7FFC,0x8000,0x8075,0x80B2, - 0x80FF,0x81C8,0x826C,0x8274,0x82B5,0x8412,0x8458,0x8467,0x8533,0x8577,0x85FF,0x8658,0x86E3,0x86E6,0x878E,0x885E, - 0x8A7E,0x8B1F,0x8B25,0x8B33,0x8B4D,0x8B80,0x8BFF,0x8C38,0x8D46,0x8D8B,0x8E10,0x8E79,0x8F01,0x8FF1,0x9000,0x9029, - 0x9082,0x924A,0x92F8,0x9342,0x940C,0x94B9,0x9580,0x95ED,0x9630,0x97D1,0x97FC,0x9810,0x9825,0x9861,0x98FB,0x990E, - 0x9970,0x9A37,0x9B36,0x9B67,0x9DBF,0x9FA3,0x9FF2,0xA000,0xA0A3,0xA0AA,0xA1C6,0xA27A,0xA2FB,0xA3AB,0xA3C2,0xA4E1, - 0xA548,0xA553,0xA585,0xA6A7,0xA71C,0xA723,0xA77E,0xA7D6,0xA894,0xA8BF,0xA8E0,0xA90A,0xAA3A,0xAA57,0xAADF,0xAAF4, - 0xAB0B,0xABF3,0xAC0C,0xAC23,0xAC55,0xAD00,0xADC4,0xADF1,0xAE02,0xAE16,0xAE6A,0xAEA7,0xAEB6,0xB040,0xB073,0xB122, - 0xB14B,0xB175,0xB177,0xB25D,0xB262,0xB332,0xB378,0xB400,0xB52E,0xB54B,0xB592,0xB59C,0xB5AD,0xB5CE,0xB5D3,0xB5D5, - 0xB613,0xB65D,0xB79F,0xB89D,0xB907,0xBC78,0xBCCC,0xBDBF,0xBE21,0xBF21,0xBF64,0xC13F,0xC2EA,0xC303,0xC385,0xC3C9, - 0xC3D8,0xC452,0xC5CB,0xC5EB,0xC62D,0xC655,0xC67B,0xC6D9,0xC728,0xC8A8,0xCAF6,0xCDAC,0xCDF7,0xCE3B,0xD075,0xD163, - 0xD203,0xD410,0xD49E,0xD70A,0xD718,0xD815,0xD876,0xD99C,0xDA29,0xDAA7,0xDBA0,0xDC8F,0xDCFA,0xDD3D,0xDD55,0xDE02, - 0xDE30,0xE000,0xE02E,0xE073,0xE1C5,0xE1CD,0xE288,0xE3AE,0xE597,0xE5C3,0xE5E3,0xE652,0xE775,0xE930,0xE947,0xEC15, - 0xEC1B,0xEC56,0xED0A,0xED93,0xED95,0xEDC2,0xEF7F,0xEFA6,0xEFB6,0xF049,0xF0C7,0xF107,0xF41F,0xF558,0xF5C8,0xF7DC, - 0xF7ED,0xF89E,0xF8B2,0xF9C9,0xFAAA,0xFB77,0xFCAF,0xFCCA,0xFD01,0xFD13,0xFD57,0xFD68,0xFDDB,0xFE07,0xFF43,0xFF65, - 0xFF85,0xFF8B,0xFF9A,0xFFD0, + 0x0152,0x0165,0x0192,0x0240,0x024F,0x026B,0x026F,0x02B8,0x0348,0x037D,0x03C9,0x03DA,0x0440,0x0468,0x0526,0x0552, + 0x055E,0x058C,0x0597,0x05DC,0x05F6,0x05FE,0x0606,0x064D,0x0661,0x0687,0x06CC,0x0752,0x07A1,0x07B9,0x07CD,0x07F7, + 0x0823,0x08A5,0x08D8,0x08E1,0x08EF,0x09AC,0x09D7,0x0A9A,0x0AA8,0x0B0C,0x0B51,0x0BBA,0x0BBE,0x0C9A,0x0D71,0x0D7B, + 0x0DCF,0x0DD4,0x0DF7,0x0E1C,0x0E4F,0x0E6A,0x0E9D,0x0EAF,0x0F2B,0x0F85,0x0FDA,0x1011,0x102F,0x103A,0x1041,0x1074, + 0x107E,0x10BF,0x10C7,0x10E5,0x1102,0x1107,0x112F,0x113A,0x116A,0x116F,0x1182,0x11A6,0x11FA,0x1220,0x126A,0x1280, + 0x12B8,0x12D7,0x130E,0x1335,0x135B,0x13C0,0x13C6,0x13EC,0x142C,0x1469,0x1474,0x14E4,0x1518,0x1536,0x1538,0x1585, + 0x15AD,0x15E1,0x16F3,0x1748,0x1756,0x178C,0x179A,0x17F4,0x1802,0x184F,0x1892,0x1903,0x1944,0x198E,0x19A1,0x19F3, + 0x1A0C,0x1A5B,0x1A89,0x1AAC,0x1AC7,0x1B11,0x1B92,0x1C31,0x1C7C,0x1C7E,0x1D01,0x1D0C,0x1D5A,0x1DC1,0x1DCF,0x1E3B, + 0x1E64,0x1E72,0x1E88,0x1F90,0x1FAE,0x1FC2,0x208C,0x20E9,0x2106,0x2117,0x2122,0x2160,0x2161,0x216E,0x21BE,0x21FE, + 0x2277,0x22D9,0x22FA,0x230C,0x23C9,0x23D8,0x2402,0x2415,0x243C,0x245A,0x24AD,0x2600,0x2632,0x266E,0x2692,0x269B, + 0x26D4,0x26FC,0x2701,0x2723,0x274D,0x2777,0x27C2,0x27E7,0x27EE,0x2865,0x2882,0x28B2,0x28B6,0x2973,0x29B0,0x29B1, + 0x2A04,0x2A33,0x2A55,0x2ADB,0x2AE7,0x2B48,0x2B7D,0x2BAA,0x2C09,0x2C2E,0x2C77,0x2D08,0x2D1F,0x2DAE,0x2DB0,0x2DB7, + 0x2DE5,0x2DF5,0x2E6E,0x2E75,0x2E9E,0x2EB6,0x2EBF,0x2ED8,0x2EE7,0x2F54,0x2F66,0x2F72,0x2F83,0x2FCB,0x3046,0x30BB, + 0x30BE,0x30CA,0x30E6,0x312C,0x3160,0x3180,0x31C7,0x31ED,0x31F8,0x3218,0x324D,0x32A9,0x32E8,0x32E9,0x32F9,0x3319, + 0x333F,0x334E,0x336C,0x3373,0x338B,0x33AE,0x33BE,0x33FB,0x3459,0x3486,0x34A5,0x34A8,0x34C3,0x34C6,0x34EB,0x3527, + 0x355A,0x357D,0x35DC,0x35F1,0x3619,0x3645,0x365C,0x36AA,0x36BC,0x3738,0x3745,0x3749,0x3752,0x3791,0x37C3,0x37DE, + 0x3804,0x380A,0x380C,0x3818,0x3920,0x3946,0x3952,0x395C,0x3999,0x39D0,0x39DB,0x3AB7,0x3B79,0x3BBD,0x3BCE,0x3C7B, + 0x3CA3,0x3D2C,0x3D50,0x3DA7,0x3DEF,0x3E7B,0x3E89,0x3EB2,0x3ED0,0x3EF2,0x3F18,0x3F42,0x3F79,0x4004,0x4069,0x406A, + 0x40BF,0x40DE,0x410B,0x410F,0x413E,0x414F,0x416B,0x41A3,0x41B3,0x41BC,0x41E3,0x426E,0x4284,0x42EA,0x42EB,0x434A, + 0x43D3,0x43E6,0x43E9,0x4411,0x444F,0x4457,0x449E,0x44B9,0x4541,0x458B,0x463E,0x4751,0x476E,0x477D,0x4793,0x47B2, + 0x47D1,0x47DF,0x4840,0x487A,0x4894,0x48C5,0x48D7,0x49B4,0x49E3,0x4A52,0x4B03,0x4B0E,0x4B21,0x4B52,0x4B70,0x4B8D, + 0x4B94,0x4BA7,0x4C14,0x4C49,0x4C72,0x4CBF,0x4D1F,0x4D35,0x4D44,0x4D86,0x4D9F,0x4DDC,0x4DF5,0x4E29,0x4E40,0x4E48, + 0x4E5A,0x4E7C,0x4E93,0x4EE5,0x4F26,0x4F36,0x4F60,0x4F6C,0x4F92,0x4FAB,0x4FBB,0x4FE5,0x4FF2,0x5137,0x514C,0x51A9, + 0x51B3,0x51F8,0x5220,0x5406,0x543B,0x5487,0x5493,0x5496,0x54B1,0x54CE,0x552C,0x558B,0x559B,0x55F4,0x5639,0x567C, + 0x56A1,0x56EE,0x577B,0x5794,0x581E,0x582E,0x5846,0x586A,0x58B1,0x58C3,0x58C9,0x596A,0x59A5,0x59CD,0x59EF,0x59F7, + 0x5A3C,0x5A81,0x5A86,0x5ABB,0x5AE5,0x5B0B,0x5B19,0x5B1C,0x5B49,0x5B5C,0x5B90,0x5BC1,0x5BC2,0x5BCF,0x5BD8,0x5BFE, + 0x5C38,0x5C75,0x5C83,0x5C9C,0x5CFC,0x5D4D,0x5D54,0x5D61,0x5D70,0x5DE0,0x5DEF,0x5DFA,0x5E30,0x5E47,0x5E57,0x5E63, + 0x5E74,0x5EB0,0x5EB3,0x5ED5,0x5F1B,0x5F5C,0x5F8F,0x5F9D,0x5FAF,0x5FD1,0x601F,0x6054,0x6062,0x6071,0x6073,0x60AA, + 0x60CE,0x6104,0x6124,0x6149,0x617F,0x6188,0x618B,0x6191,0x61B2,0x61EB,0x61F3,0x621C,0x6226,0x623A,0x627D,0x627F, + 0x62F9,0x6364,0x63DE,0x63F3,0x6452,0x645F,0x64A5,0x64AE,0x64D3,0x64E6,0x651A,0x659D,0x65D3,0x65D4,0x65F2,0x6631, + 0x66F1,0x6739,0x68E0,0x6914,0x6955,0x697B,0x6990,0x6993,0x6A01,0x6A45,0x6A52,0x6A64,0x6A85,0x6AFA,0x6B08,0x6B16, + 0x6B75,0x6B79,0x6BF6,0x6BF8,0x6C2F,0x6C3C,0x6CA5,0x6CBD,0x6CD6,0x6CE3,0x6CF9,0x6CFA,0x6D0E,0x6D16,0x6D1E,0x6D3E, + 0x6D6F,0x6D80,0x6D8B,0x6DCF,0x6DFD,0x6E73,0x6EC5,0x6ECF,0x6F14,0x6F6F,0x6FD0,0x7033,0x704C,0x7098,0x7106,0x71A3, + 0x721B,0x7235,0x7242,0x7278,0x72B7,0x7319,0x7359,0x73B9,0x73F5,0x7427,0x742F,0x7432,0x7452,0x745F,0x747F,0x7484, + 0x749D,0x74C8,0x74D7,0x74D8,0x74FB,0x74FE,0x756A,0x7575,0x7576,0x7588,0x75A9,0x75DE,0x7604,0x7640,0x764D,0x773C, + 0x7742,0x7751,0x7766,0x77C3,0x77D7,0x77E6,0x7860,0x786B,0x7895,0x78B0,0x78F1,0x78F3,0x7976,0x797F,0x798A,0x798F, + 0x79AB,0x79E7,0x79F9,0x7A1D,0x7A24,0x7A47,0x7A6B,0x7AB5,0x7AE2,0x7B64,0x7B68,0x7BBC,0x7C2E,0x7C77,0x7C7D,0x7C8A, + 0x7CBA,0x7D0D,0x7D12,0x7D2C,0x7E0E,0x7E4B,0x7E66,0x7E9D,0x7EB5,0x7EBA,0x7EC5,0x7F10,0x7F17,0x7F1B,0x7F26,0x7F5F, + 0x7FB8,0x7FC1,0x7FCE,0x7FFC,0x8075,0x80A2,0x80A5,0x80B2,0x80CB,0x80E7,0x80FF,0x8116,0x8183,0x81C8,0x8205,0x826C, + 0x8274,0x82B5,0x82FD,0x830D,0x8330,0x833C,0x8346,0x8349,0x8412,0x8413,0x8458,0x8467,0x8486,0x849D,0x8533,0x8540, + 0x8577,0x8586,0x858E,0x85AD,0x85FF,0x862D,0x863D,0x863E,0x8658,0x86E3,0x86E6,0x86E9,0x8739,0x873C,0x878E,0x8797, + 0x879F,0x87EB,0x87FD,0x8801,0x8812,0x8828,0x885E,0x88C3,0x890D,0x891F,0x899E,0x89BA,0x89D9,0x8A0F,0x8A7E,0x8A87, + 0x8AC1,0x8B1F,0x8B25,0x8B33,0x8B4D,0x8B76,0x8B80,0x8BFF,0x8C38,0x8C4A,0x8CF2,0x8D46,0x8D86,0x8D8B,0x8DAE,0x8DEF, + 0x8E10,0x8E79,0x8F01,0x8F7C,0x8FCF,0x8FF1,0x9000,0x9029,0x9082,0x9130,0x9166,0x91D6,0x924A,0x9277,0x92F8,0x9342, + 0x9375,0x940C,0x942A,0x9496,0x94B9,0x94D3,0x9512,0x9521,0x953A,0x9551,0x9580,0x95ED,0x9625,0x962D,0x9630,0x963A, + 0x9664,0x96EF,0x971B,0x97D1,0x97FC,0x9810,0x981A,0x9825,0x9828,0x9861,0x98E3,0x98FB,0x990E,0x9970,0x998A,0x99A2, + 0x99DA,0x99F8,0x9A37,0x9A6F,0x9A8A,0x9AFE,0x9B06,0x9B36,0x9B54,0x9B67,0x9B81,0x9B91,0x9CC5,0x9D17,0x9D41,0x9D99, + 0x9DBF,0x9DEA,0x9E16,0x9E1D,0x9E5A,0x9EAC,0x9F39,0x9FA3,0x9FED,0x9FF2,0xA020,0xA03C,0xA081,0xA0A3,0xA0AA,0xA145, + 0xA1C6,0xA221,0xA27A,0xA2FB,0xA3AB,0xA3C2,0xA457,0xA458,0xA49E,0xA4E1,0xA548,0xA553,0xA585,0xA597,0xA5EA,0xA655, + 0xA6A7,0xA6D9,0xA6F6,0xA71C,0xA723,0xA75F,0xA77E,0xA7D6,0xA88B,0xA894,0xA8BF,0xA8C8,0xA8E0,0xA90A,0xA933,0xAA21, + 0xAA3A,0xAA57,0xAA5E,0xAADF,0xAAF4,0xAB0B,0xAB52,0xAB8F,0xABC3,0xABD3,0xABF3,0xAC0C,0xAC23,0xAC55,0xACCF,0xACD5, + 0xACDF,0xAD00,0xAD12,0xADC4,0xADF1,0xAE02,0xAE16,0xAE45,0xAE6A,0xAEA7,0xAEB6,0xAFFA,0xB015,0xB040,0xB05B,0xB073, + 0xB099,0xB10A,0xB122,0xB14B,0xB155,0xB160,0xB175,0xB177,0xB221,0xB238,0xB25D,0xB262,0xB271,0xB30B,0xB332,0xB378, + 0xB3FF,0xB400,0xB437,0xB473,0xB4A9,0xB4F1,0xB52E,0xB54B,0xB556,0xB57D,0xB592,0xB59C,0xB5AD,0xB5CE,0xB5D3,0xB5D5, + 0xB603,0xB613,0xB65D,0xB75E,0xB761,0xB79F,0xB7A0,0xB7BC,0xB7C3,0xB7F8,0xB893,0xB89D,0xB8F5,0xB907,0xB989,0xB9B8, + 0xBAE5,0xBB0D,0xBB0F,0xBBA3,0xBBBC,0xBBD3,0xBBE4,0xBC3D,0xBC78,0xBC98,0xBCCC,0xBD5D,0xBD67,0xBDBF,0xBE10,0xBE21, + 0xBE57,0xBEA2,0xBEBF,0xBF21,0xBF22,0xBF3F,0xBF64,0xBF8B,0xC01B,0xC022,0xC036,0xC041,0xC04D,0xC055,0xC075,0xC095, + 0xC0B4,0xC0DC,0xC0E4,0xC133,0xC13F,0xC156,0xC1DB,0xC2EA,0xC303,0xC36C,0xC385,0xC394,0xC3C9,0xC3D8,0xC3DB,0xC434, + 0xC452,0xC4AD,0xC5CB,0xC5E0,0xC5EB,0xC5F2,0xC625,0xC62D,0xC655,0xC67B,0xC68E,0xC6B3,0xC6D9,0xC709,0xC728,0xC78E, + 0xC7A4,0xC838,0xC873,0xC887,0xC8A1,0xC8A8,0xC8E6,0xC8F6,0xC952,0xC98B,0xC9AD,0xCA13,0xCA4D,0xCA99,0xCAB9,0xCAF6, + 0xCB1B,0xCBC1,0xCBE6,0xCC3F,0xCC77,0xCD13,0xCDAC,0xCDC5,0xCDF7,0xCE16,0xCE32,0xCE3B,0xCEB8,0xCF0F,0xCF80,0xCFF7, + 0xD062,0xD075,0xD0B2,0xD0D5,0xD159,0xD163,0xD19D,0xD1DD,0xD1E9,0xD203,0xD23D,0xD2AB,0xD2B1,0xD2E0,0xD305,0xD410, + 0xD422,0xD446,0xD47B,0xD47F,0xD493,0xD49E,0xD591,0xD59C,0xD5A0,0xD70A,0xD710,0xD718,0xD815,0xD876,0xD91D,0xD921, + 0xD96B,0xD99C,0xDA29,0xDA77,0xDAA7,0xDB38,0xDBA0,0xDC31,0xDC33,0xDC5A,0xDC8F,0xDCFA,0xDD0B,0xDD3D,0xDD55,0xDE02, + 0xDE30,0xDE57,0xDE8A,0xDED3,0xDED7,0xDF38,0xDF9A,0xE02E,0xE073,0xE114,0xE125,0xE164,0xE195,0xE1A6,0xE1C5,0xE1CD, + 0xE1D5,0xE1D6,0xE1F1,0xE222,0xE22C,0xE25F,0xE288,0xE297,0xE3A0,0xE3AE,0xE3D3,0xE405,0xE434,0xE437,0xE49D,0xE4AC, + 0xE4BE,0xE50E,0xE51F,0xE52F,0xE53B,0xE597,0xE5A9,0xE5C3,0xE5E3,0xE5F1,0xE652,0xE659,0xE6AA,0xE6D7,0xE6DF,0xE6EE, + 0xE76D,0xE775,0xE795,0xE7A2,0xE7B5,0xE7CE,0xE8DD,0xE8EB,0xE930,0xE944,0xE947,0xE99F,0xEA1C,0xEACB,0xEBBA,0xEC15, + 0xEC1B,0xEC36,0xEC56,0xED0A,0xED17,0xED24,0xED5A,0xED87,0xED93,0xED95,0xED99,0xEDC2,0xEDEB,0xEE1F,0xEEC6,0xEF04, + 0xEF63,0xEF7F,0xEFA6,0xEFB6,0xEFCC,0xEFD1,0xEFD8,0xF00A,0xF049,0xF055,0xF0C7,0xF107,0xF108,0xF132,0xF16D,0xF1E7, + 0xF221,0xF24B,0xF2E3,0xF2F6,0xF32A,0xF3FE,0xF412,0xF41F,0xF429,0xF4F7,0xF52B,0xF558,0xF585,0xF5C8,0xF66E,0xF675, + 0xF685,0xF6A3,0xF6D7,0xF78A,0xF7A6,0xF7D7,0xF7DC,0xF7ED,0xF82E,0xF84C,0xF89E,0xF8B2,0xF982,0xF9A9,0xF9C9,0xFA0B, + 0xFA6B,0xFAAA,0xFB77,0xFCAF,0xFCB8,0xFCCA,0xFD01,0xFD0B,0xFD13,0xFD38,0xFD57,0xFD5D,0xFD68,0xFD6D,0xFD86,0xFDDB, + 0xFDDC,0xFE07,0xFE33,0xFEA6,0xFF43,0xFF48,0xFF65,0xFF85,0xFF8B,0xFF8C,0xFF9A,0xFFD0, }; #if 0 diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ikm.c b/Frameworks/vgmstream/vgmstream/src/meta/ikm.c new file mode 100644 index 000000000..d6044c9fd --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/ikm.c @@ -0,0 +1,125 @@ +#include "meta.h" +#include "../coding/coding.h" + + +/* IKM - MiCROViSiON PS2 container [Zwei (PS2)] */ +VGMSTREAM * init_vgmstream_ikm_ps2(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count; + + + /* checks */ + if ( !check_extensions(streamFile,"ikm") ) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x494B4D00) /* "IKM\0" */ + goto fail; + + if (read_32bitBE(0x40,streamFile) != 0x41535400) /* "AST\0" */ + goto fail; + /* 0x20: type 03? */ + + loop_flag = (read_32bitLE(0x14, streamFile) > 0); + channel_count = read_32bitLE(0x50, streamFile); + start_offset = 0x800; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_IKM; + vgmstream->sample_rate = read_32bitLE(0x44, streamFile); + vgmstream->num_samples = ps_bytes_to_samples(read_32bitLE(0x4c, streamFile), channel_count); + vgmstream->loop_start_sample = read_32bitLE(0x14, streamFile); + vgmstream->loop_end_sample = read_32bitLE(0x18, streamFile); + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x10; /* @0x40 / channels */ + + if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* IKM - MiCROViSiON PC container [Chaos Legion (PC)] */ +VGMSTREAM * init_vgmstream_ikm_pc(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + + + /* checks */ + if ( !check_extensions(streamFile,"ikm") ) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x494B4D00) /* "IKM\0" */ + goto fail; + if (read_32bitBE(0x30,streamFile) != 0x4F676753) /* "OggS" */ + goto fail; + /* 0x20: type 01? */ + + start_offset = 0x30; +#ifdef VGM_USE_VORBIS + { + ogg_vorbis_meta_info_t ovmi = {0}; + + ovmi.meta_type = meta_IKM; + ovmi.loop_start = read_32bitLE(0x14, streamFile); + ovmi.loop_end = read_32bitLE(0x18, streamFile); + ovmi.loop_end_found = ovmi.loop_end; + ovmi.loop_flag = ovmi.loop_end > 0; + ovmi.stream_size = read_32bitLE(0x24, streamFile); + + vgmstream = init_vgmstream_ogg_vorbis_callbacks(streamFile, NULL, start_offset, &ovmi); + } +#else + goto fail; +#endif + + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* IKM - MiCROViSiON PSP container [The Legend of Heroes: A Tear of Vermillion (PSP)] */ +VGMSTREAM * init_vgmstream_ikm_psp(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + off_t start_offset; + size_t data_size; + + + /* checks */ + if ( !check_extensions(streamFile,"ikm") ) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x494B4D00) /* "IKM\0" */ + goto fail; + if (read_32bitBE(0x800,streamFile) != 0x52494646) /* "RIFF" */ + goto fail; + /* 0x20: type 00? */ + + /* loop values (pre-adjusted without encoder delay) at 0x14/18 are found in the RIFF too */ + data_size = read_32bitLE(0x24, streamFile); + start_offset = 0x800; + + temp_streamFile = setup_subfile_streamfile(streamFile, start_offset, data_size, "at3"); + if (!temp_streamFile) goto fail; + + vgmstream = init_vgmstream_riff(temp_streamFile); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_IKM; + + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/imc.c b/Frameworks/vgmstream/vgmstream/src/meta/imc.c index 5cdfe41c1..6c2b1300f 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/imc.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/imc.c @@ -29,19 +29,9 @@ VGMSTREAM * init_vgmstream_imc(STREAMFILE *streamFile) { if (interleave*blocks + start_offset != file_size) goto fail; - /* remove padding (important to play gapless subsongs, happens even for mono) */ - { - off_t min_offset = file_size - interleave; - off_t offset = file_size - 0x10; + data_size = file_size - start_offset; + data_size -= ps_find_padding(streamFile, start_offset, data_size, channel_count, interleave, 0); - data_size = file_size - start_offset; - while (offset > min_offset) { - if (read_32bitLE(offset, streamFile) != 0) - break; - data_size -= 0x10*channel_count; - offset -= 0x10; - } - } /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count, loop_flag); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ktss.c b/Frameworks/vgmstream/vgmstream/src/meta/ktss.c index 76339eb71..02f36a47a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ktss.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ktss.c @@ -4,10 +4,10 @@ VGMSTREAM * init_vgmstream_ktss(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; int loop_flag, channel_count; - int8_t version; + int8_t version, num_layers, codec_id; int32_t loop_length, coef_start_offset, coef_spacing; off_t start_offset; - int8_t channelMultiplier; + size_t data_size, skip = 0; if (!check_extensions(streamFile, "kns,ktss")) goto fail; @@ -15,30 +15,18 @@ VGMSTREAM * init_vgmstream_ktss(STREAMFILE *streamFile) { if (read_32bitBE(0, streamFile) != 0x4B545353) /* "KTSS" */ goto fail; - /* check type details */ - version = read_8bit(0x22, streamFile); - if (version == 1) { - coef_start_offset = 0x40; - coef_spacing = 0x2e; - } - else if (version == 3) { // Fire Emblem Warriors (Switch) - coef_start_offset = 0x5c; - coef_spacing = 0x60; - } - else - goto fail; - + codec_id = read_8bit(0x20, streamFile); loop_length = read_32bitLE(0x38, streamFile); loop_flag = loop_length > 0; - // For unknown reasons, a channel multiplier is necessary in Hyrule Warriors (Switch) - // It seems to be present in other Koei Tecmo KNS but the channel count was always - // explicitly defined in the 0x29 byte. Here, 10 channel files have '2' in 0x29* - // and '5' in 0x28 whereas previous titles usually contained '1' - // This is super meh on KT's part but whatever - channelMultiplier = read_8bit(0x28, streamFile); + // A layered stream/track model seems to be used in Hyrule Warriors (Switch). + // It's also present in other Koei Tecmo KNS but the channel count was always + // explicitly defined in the 0x29 byte and the number of layers was set to 1. + // Here, 10 channel files are set up with 2 channels in 5 layers. + // Super hacky on KT's part and ours to implement but it works. + num_layers = read_8bit(0x28, streamFile); - channel_count = read_8bit(0x29, streamFile) * channelMultiplier; + channel_count = read_8bit(0x29, streamFile) * num_layers; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count, loop_flag); @@ -46,19 +34,53 @@ VGMSTREAM * init_vgmstream_ktss(STREAMFILE *streamFile) { /* fill in the vital statistics */ vgmstream->num_samples = read_32bitLE(0x30, streamFile); - vgmstream->sample_rate = (uint16_t)read_16bitLE(0x2c, streamFile); + vgmstream->sample_rate = read_32bitLE(0x2c, streamFile); vgmstream->loop_start_sample = read_32bitLE(0x34, streamFile); vgmstream->loop_end_sample = vgmstream->loop_start_sample + loop_length; - - vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = layout_interleave; vgmstream->meta_type = meta_KTSS; - - vgmstream->interleave_block_size = 0x8; - - dsp_read_coefs_le(vgmstream, streamFile, coef_start_offset, coef_spacing); start_offset = read_32bitLE(0x24, streamFile) + 0x20; + switch (codec_id) { + case 0x2: /* DSP ADPCM - Hyrule Warriors, Fire Emblem Warriors, and other Koei Tecmo games */ + /* check type details */ + version = read_8bit(0x22, streamFile); + if (version == 1) { + coef_start_offset = 0x40; + coef_spacing = 0x2e; + } + else if (version == 3) { // Fire Emblem Warriors (Switch) + coef_start_offset = 0x5c; + coef_spacing = 0x60; + } + else + goto fail; + + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x8; + dsp_read_coefs_le(vgmstream, streamFile, coef_start_offset, coef_spacing); + break; + +#ifdef VGM_USE_FFMPEG + case 0x9: /* Opus - Dead or Alive Xtreme 3: Scarlet */ + data_size = read_32bitLE(0x44, streamFile); + { + vgmstream->codec_data = init_ffmpeg_switch_opus(streamFile, start_offset, data_size, vgmstream->channels, skip, vgmstream->sample_rate); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + if (vgmstream->num_samples == 0) { + vgmstream->num_samples = switch_opus_get_samples(start_offset, data_size, streamFile) - skip; + } + } + break; + + default: + goto fail; +#endif + } + if (!vgmstream_open_stream(vgmstream, streamFile, start_offset)) goto fail; return vgmstream; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/meta.h b/Frameworks/vgmstream/vgmstream/src/meta/meta.h index 50ce55b07..a11110950 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/meta.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/meta.h @@ -49,6 +49,7 @@ VGMSTREAM * init_vgmstream_dsp_switch_audio(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_dsp_sps_n1(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_dsp_itl_ch(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_dsp_adpcmx(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_dsp_ds2(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_csmp(STREAMFILE *streamFile); @@ -57,7 +58,7 @@ VGMSTREAM * init_vgmstream_rfrm(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ps2_ads(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ps2_ads_container(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_ps2_npsf(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_nps(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_rs03(STREAMFILE *streamFile); @@ -116,6 +117,7 @@ typedef struct { off_t stream_size; int total_subsongs; + int disable_reordering; /* decryption setup */ void (*decryption_callback)(void *ptr, size_t size, size_t nmemb, void *datasource); @@ -209,7 +211,9 @@ VGMSTREAM * init_vgmstream_leg(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_filp(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_ikm(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ikm_ps2(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ikm_pc(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ikm_psp(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_sfs(STREAMFILE * streamFile); @@ -358,7 +362,7 @@ VGMSTREAM * init_vgmstream_ps2_joe(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_vgs(STREAMFILE * streamFile); -VGMSTREAM * init_vgmstream_dc_dcsw_dcs(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_dcs_wav(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_mul(STREAMFILE * streamFile); @@ -511,7 +515,7 @@ VGMSTREAM * init_vgmstream_ngc_nst_dsp(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_baf(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_baf_badrip(STREAMFILE* streamFile); -VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE* streamFile); +VGMSTREAM * init_vgmstream_msf(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_nub_vag(STREAMFILE* streamFile); @@ -550,7 +554,7 @@ VGMSTREAM * init_vgmstream_pc_adp_otns(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_eb_sfx(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_eb_sf0(STREAMFILE* streamFile); -VGMSTREAM * init_vgmstream_ps2_mtaf(STREAMFILE* streamFile); +VGMSTREAM * init_vgmstream_mtaf(STREAMFILE* streamFile); VGMSTREAM * init_vgmstream_tun(STREAMFILE* streamFile); @@ -627,7 +631,8 @@ VGMSTREAM * init_vgmstream_ta_aac_vita(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_va3(STREAMFILE *streamFile); -VGMSTREAM * init_vgmstream_ps3_mta2(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_mta2(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_mta2_container(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ngc_ulw(STREAMFILE * streamFile); @@ -638,6 +643,7 @@ VGMSTREAM * init_vgmstream_wii_04sw(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_txth(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_schl(STREAMFILE *streamFile); +VGMSTREAM * init_vgmstream_ea_schl_video(STREAMFILE *streamFile); VGMSTREAM * init_vgmstream_ea_bnk(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_abk(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_hdr_dat(STREAMFILE * streamFile); @@ -670,6 +676,7 @@ VGMSTREAM * init_vgmstream_naac(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ubi_sm(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ezw(STREAMFILE * streamFile); @@ -681,6 +688,7 @@ VGMSTREAM * init_vgmstream_ea_sps(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_abk_eaac(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_hdr_sth_dat(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_mpf_mus_eaac(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ea_tmx(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_sbr(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_ea_sbr_harmony(STREAMFILE * streamFile); @@ -830,4 +838,20 @@ VGMSTREAM * init_vgmstream_dsf(STREAMFILE * streamFile); VGMSTREAM * init_vgmstream_208(STREAMFILE * streamFile); +VGMSTREAM * init_vgmstream_ffdl(STREAMFILE * streamFile); + +VGMSTREAM * init_vgmstream_mus_vc(STREAMFILE * streamFile); + +VGMSTREAM * init_vgmstream_strm_abylight(STREAMFILE * streamFile); + +VGMSTREAM * init_vgmstream_sfh(STREAMFILE * streamFile); + +VGMSTREAM * init_vgmstream_msf_konami(STREAMFILE* streamFile); + +VGMSTREAM * init_vgmstream_xwma_konami(STREAMFILE* streamFile); + +VGMSTREAM * init_vgmstream_9tav(STREAMFILE* streamFile); + +VGMSTREAM * init_vgmstream_fsb5_fev_bank(STREAMFILE * streamFile); + #endif /*_META_H*/ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mib_mih.c b/Frameworks/vgmstream/vgmstream/src/meta/mib_mih.c index b3f3f6383..6e451b562 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/mib_mih.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/mib_mih.c @@ -7,7 +7,7 @@ VGMSTREAM * init_vgmstream_mib_mih(STREAMFILE *streamFile) { STREAMFILE * streamHeader = NULL; off_t start_offset; size_t data_size, frame_size, frame_last, frame_count; - int channel_count, loop_flag; + int channel_count, loop_flag, sample_rate; /* check extension */ if (!check_extensions(streamFile, "mib")) @@ -19,20 +19,22 @@ VGMSTREAM * init_vgmstream_mib_mih(STREAMFILE *streamFile) { if (read_32bitBE(0x00,streamHeader) != 0x40000000) /* header size */ goto fail; - loop_flag = 0; /* MIB+MIH don't PS-ADPCM loop flags */ - channel_count = read_32bitLE(0x08,streamHeader); + loop_flag = 0; /* MIB+MIH don't loop (nor use PS-ADPCM flags) per spec */ start_offset = 0x00; - /* 0x04: padding (0x20, MIH header must be multiple of 0x40) */ - frame_last = (uint16_t)read_16bitLE(0x05,streamHeader); - frame_size = read_32bitLE(0x10,streamHeader); - frame_count = read_32bitLE(0x14,streamHeader); + /* 0x04: padding size (always 0x20, MIH header must be multiple of 0x40) */ + frame_last = (uint32_t)read_32bitLE(0x05,streamHeader) & 0x00FFFFFF; /* 24b */ + channel_count = read_32bitLE(0x08,streamHeader); + sample_rate = read_32bitLE(0x0c,streamHeader); + frame_size = read_32bitLE(0x10,streamHeader); + frame_count = read_32bitLE(0x14,streamHeader); if (frame_count == 0) { /* rarely [Gladius (PS2)] */ frame_count = get_streamfile_size(streamFile) / (frame_size * channel_count); } data_size = frame_count * frame_size; - data_size -= frame_last ? (frame_size-frame_last) : 0; /* last frame has less usable data */ + if (frame_last) + data_size -= frame_size - frame_last; /* last frame has less usable data */ data_size *= channel_count; @@ -40,7 +42,7 @@ VGMSTREAM * init_vgmstream_mib_mih(STREAMFILE *streamFile) { vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; - vgmstream->sample_rate = read_32bitLE(0x0C,streamHeader); + vgmstream->sample_rate = sample_rate; vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); vgmstream->meta_type = meta_PS2_MIB_MIH; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mp4.c b/Frameworks/vgmstream/vgmstream/src/meta/mp4.c index ac53aa53e..5471fe0df 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/mp4.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/mp4.c @@ -171,11 +171,10 @@ VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset = 0; int loop_flag = 0; - int32_t num_samples = 0, loop_start_sample = 0, loop_end_sample = 0; + int32_t loop_start_sample = 0, loop_end_sample = 0; size_t filesize; off_t atom_offset; size_t atom_size; - int is_ffdl = 0; ffmpeg_codec_data *ffmpeg_data = NULL; @@ -188,31 +187,6 @@ VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE *streamFile) { filesize = streamFile->get_size(streamFile); - /* check header for Final Fantasy Dimensions */ - if (read_32bitBE(0x00,streamFile) == 0x4646444C) { /* "FFDL" (any kind of file) */ - is_ffdl = 1; - if (read_32bitBE(0x04,streamFile) == 0x6D747873) { /* "mtxs" (bgm file) */ - /* this value is erratic so we'll use FFmpeg's num_samples - * (can be bigger = silence-padded, or smaller = cut; doesn't matter for looping though)*/ - num_samples = read_32bitLE(0x08,streamFile); - /* loop samples are within num_samples, and don't have encoder delay (loop_start=0 starts from encoder_delay) */ - loop_start_sample = read_32bitLE(0x0c,streamFile); - loop_end_sample = read_32bitLE(0x10,streamFile); - loop_flag = !(loop_start_sample==0 && loop_end_sample==num_samples); - start_offset = 0x14; - - /* some FFDL have muxed streams ("FFDL" + "mtxs" data1 + mp4 data1 + "mtxs" data2 + mp4 data2 + etc) - * check if there is anything after the first mp4 data */ - if (!find_atom_be(streamFile, 0x6D646174, start_offset, &atom_offset, &atom_size)) goto fail; /* "mdat" */ - if (atom_offset-8 + atom_size < filesize && read_32bitBE(atom_offset-8 + atom_size,streamFile) == 0x6D747873) { /*"mtxs"*/ - VGM_LOG("FFDL: multiple streams found\n"); - filesize = atom_offset-8 + atom_size; /* clamp size, though FFmpeg will ignore the extra data anyway */ - } - } else { - start_offset = 0x4; /* some SEs contain "ftyp" after "FFDL" */ - } - } - /* check header */ if ( read_32bitBE(start_offset+0x04,streamFile) != 0x66747970) /* atom size @0x00 + "ftyp" @0x04 */ goto fail; @@ -221,7 +195,7 @@ VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE *streamFile) { if ( !ffmpeg_data ) goto fail; /* Tales of Hearts iOS has loop info in the first "free" atom */ - if (!is_ffdl && find_atom_be(streamFile, 0x66726565, start_offset, &atom_offset, &atom_size)) { /* "free" */ + if (find_atom_be(streamFile, 0x66726565, start_offset, &atom_offset, &atom_size)) { /* "free" */ if (read_32bitBE(atom_offset,streamFile) == 0x4F700002 && (atom_size == 0x38 || atom_size == 0x40)) { /* make sure it's ToHr "free" */ /* 0x00: id? 0x04/8: s_rate; 0x10: num_samples (without padding, same as FFmpeg's) */ @@ -242,12 +216,13 @@ VGMSTREAM * init_vgmstream_mp4_aac_ffmpeg(STREAMFILE *streamFile) { vgmstream->layout_type = layout_none; vgmstream->meta_type = meta_MP4; - vgmstream->num_samples = ffmpeg_data->totalSamples; vgmstream->sample_rate = ffmpeg_data->sampleRate; - vgmstream->channels = ffmpeg_data->channels; + vgmstream->num_samples = ffmpeg_data->totalSamples; vgmstream->loop_start_sample = loop_start_sample; vgmstream->loop_end_sample = loop_end_sample; + vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data); + return vgmstream; fail: @@ -259,11 +234,7 @@ fail: return NULL; } -/** - * Almost the same as streamfile.c's find_chunk but for "atom" chunks, which have chunk_size first because Apple. - * - * returns 0 on failure - */ +/* Almost the same as streamfile.c's find_chunk but for "atom" chunks, which have chunk_size first because Apple, returns 0 on failure */ static int find_atom_be(STREAMFILE *streamFile, uint32_t atom_id, off_t start_offset, off_t *out_atom_offset, size_t *out_atom_size) { size_t filesize; off_t current_atom = start_offset; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps3_msf.c b/Frameworks/vgmstream/vgmstream/src/meta/msf.c similarity index 89% rename from Frameworks/vgmstream/vgmstream/src/meta/ps3_msf.c rename to Frameworks/vgmstream/vgmstream/src/meta/msf.c index 52e10b064..8da863fda 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps3_msf.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/msf.c @@ -2,12 +2,12 @@ #include "../coding/coding.h" /* MSF - Sony's PS3 SDK format (MultiStream File) */ -VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { +VGMSTREAM * init_vgmstream_msf(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; uint32_t data_size, loop_start = 0, loop_end = 0; - uint32_t id, codec_id, flags; - int loop_flag = 0, channel_count; + uint32_t codec, flags; + int loop_flag, channel_count, sample_rate; /* checks */ @@ -17,19 +17,19 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { if (!check_extensions(streamFile,"msf,at3,mp3")) goto fail; - start_offset = 0x40; - /* check header "MSF" + version-char, usually: * 0x01, 0x02, 0x30 ("0"), 0x35 ("5"), 0x43 ("C") (last/most common version) */ - id = read_32bitBE(0x00,streamFile); - if ((id & 0xffffff00) != 0x4D534600) goto fail; + if ((read_32bitBE(0x00,streamFile) & 0xffffff00) != 0x4D534600) /* "MSF\0" */ + goto fail; + start_offset = 0x40; - codec_id = read_32bitBE(0x04,streamFile); + codec = read_32bitBE(0x04,streamFile); channel_count = read_32bitBE(0x08,streamFile); data_size = read_32bitBE(0x0C,streamFile); /* without header */ if (data_size == 0xFFFFFFFF) /* unneeded? */ data_size = get_streamfile_size(streamFile) - start_offset; + sample_rate = read_32bitBE(0x10,streamFile); /* byte flags, not in MSFv1 or v2 * 0x01/02/04/08: loop marker 0/1/2/3 @@ -56,17 +56,15 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; - vgmstream->sample_rate = read_32bitBE(0x10,streamFile); - /* sample rate hack for strange MSFv1 files (PS ADPCM only?) */ - if (vgmstream->sample_rate == 0x00000000) - vgmstream->sample_rate = 48000; + vgmstream->meta_type = meta_MSF; + vgmstream->sample_rate = sample_rate; + if (vgmstream->sample_rate == 0) /* some MSFv1 (PS-ADPCM only?) [Megazone 23 - Aoi Garland (PS3)] */ + vgmstream->sample_rate = 48000; - vgmstream->meta_type = meta_PS3_MSF; - - switch (codec_id) { + switch (codec) { case 0x00: /* PCM (Big Endian) */ case 0x01: { /* PCM (Little Endian) [Smash Cars (PS3)] */ - vgmstream->coding_type = codec_id==0 ? coding_PCM16BE : coding_PCM16LE; + vgmstream->coding_type = codec==0 ? coding_PCM16BE : coding_PCM16LE; vgmstream->layout_type = channel_count == 1 ? layout_none : layout_interleave; vgmstream->interleave_block_size = 2; @@ -85,7 +83,7 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { case 0x03: { /* PS ADPCM [Smash Cars (PS3)] */ vgmstream->coding_type = coding_PSX; - vgmstream->layout_type = channel_count == 1 ? layout_none : layout_interleave; + vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x10; vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); @@ -105,8 +103,8 @@ VGMSTREAM * init_vgmstream_ps3_msf(STREAMFILE *streamFile) { uint8_t buf[100]; int32_t bytes, block_size, encoder_delay, joint_stereo; - block_size = (codec_id==4 ? 0x60 : (codec_id==5 ? 0x98 : 0xC0)) * vgmstream->channels; - joint_stereo = (codec_id==4); /* interleaved joint stereo (ch must be even) */ + block_size = (codec==4 ? 0x60 : (codec==5 ? 0x98 : 0xC0)) * vgmstream->channels; + joint_stereo = (codec==4); /* interleaved joint stereo (ch must be even) */ /* MSF skip samples: from tests with MSEnc and real files (ex. TTT2 eddy.msf v43, v01 demos) seems like 1162 is consistent. * Atelier Rorona bt_normal01 needs it to properly skip the beginning garbage but usually doesn't matter. diff --git a/Frameworks/vgmstream/vgmstream/src/meta/msf_banpresto.c b/Frameworks/vgmstream/vgmstream/src/meta/msf_banpresto.c index ca63a961b..230cb9225 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/msf_banpresto.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/msf_banpresto.c @@ -19,7 +19,7 @@ VGMSTREAM * init_vgmstream_msf_banpresto_wmsf(STREAMFILE *streamFile) { temp_streamFile = setup_subfile_streamfile(streamFile, subfile_offset,subfile_size, NULL); if (!temp_streamFile) goto fail; - vgmstream = init_vgmstream_ps3_msf(temp_streamFile); + vgmstream = init_vgmstream_msf(temp_streamFile); if (!vgmstream) goto fail; close_streamfile(temp_streamFile); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/msf_konami.c b/Frameworks/vgmstream/vgmstream/src/meta/msf_konami.c new file mode 100644 index 000000000..6c8b5fe0c --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/msf_konami.c @@ -0,0 +1,58 @@ +#include "meta.h" +#include "../coding/coding.h" + + +/* MSFC - Konami (Armature?) variation [Metal Gear Solid 2 HD (PS3), Metal Gear Solid 3 HD (PS3)] */ +VGMSTREAM * init_vgmstream_msf_konami(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + uint32_t codec; + int loop_flag, channel_count, sample_rate; + size_t data_size; + + + /* checks */ + if (!check_extensions(streamFile,"msf")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x4D534643) /* "MSFC" */ + goto fail; + + start_offset = 0x20; + + codec = read_32bitBE(0x04,streamFile); + channel_count = read_32bitBE(0x08,streamFile); + sample_rate = read_32bitBE(0x0c,streamFile); + data_size = read_32bitBE(0x10,streamFile); /* without header */ + if (data_size + start_offset != get_streamfile_size(streamFile)) + goto fail; + loop_flag = 0; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_MSF_KONAMI; + vgmstream->sample_rate = sample_rate; + + switch (codec) { + case 0x01: + vgmstream->coding_type = coding_PSX; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x10; + + vgmstream->num_samples = ps_bytes_to_samples(data_size, channel_count); + break; + + default: + goto fail; + } + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mta2.c b/Frameworks/vgmstream/vgmstream/src/meta/mta2.c new file mode 100644 index 000000000..48023d375 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/mta2.c @@ -0,0 +1,133 @@ +#include "meta.h" +#include "mta2_streamfile.h" + + +/* MTA2 - found in Metal Gear Solid 4 (PS3) */ +VGMSTREAM * init_vgmstream_mta2(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate; + int32_t loop_start, loop_end; + uint32_t sample_rate_int; + + + /* checks */ + if ( !check_extensions(streamFile,"mta2")) + goto fail; + + if (read_32bitBE(0x00,streamFile) != 0x4d544132) /* "MTA2" */ + goto fail; + /* allow truncated files for now? */ + //if (read_32bitBE(0x04, streamFile) + 0x08 != get_streamfile_size(streamFile)) + // goto fail; + + /* base header (everything is very similar to MGS3's MTAF but BE) */ + /* 0x08(4): version? (1), 0x0c(52): null */ + + /* HEAD chunk */ + if (read_32bitBE(0x40, streamFile) != 0x48454144) /* "HEAD" */ + goto fail; + if (read_32bitBE(0x44, streamFile) != 0xB0) /* HEAD size */ + goto fail; + + /* 0x48(4): null, 0x4c: ? (0x10), 0x50(4): 0x7F (vol?), 0x54(2): 0x40 (pan?) */ + channel_count = read_16bitBE(0x56, streamFile); /* counting all tracks */ + /* 0x60(4): full block size (0x110 * channels), indirectly channels_per_track = channels / (block_size / 0x110) */ + /* 0x80 .. 0xf8: null */ + + loop_start = read_32bitBE(0x58, streamFile); + loop_end = read_32bitBE(0x5c, streamFile); + loop_flag = (loop_start != loop_end); /* also flag possibly @ 0x73 */ +#if 0 + /* those values look like some kind of loop offsets */ + if (loop_start/0x100 != read_32bitBE(0x68, streamFile) || + loop_end /0x100 != read_32bitBE(0x6C, streamFile) ) { + VGM_LOG("MTA2: wrong loop points\n"); + goto fail; + } +#endif + + sample_rate_int = read_32bitBE(0x7c, streamFile); + if (sample_rate_int) { /* sample rate in 32b float (WHY?) typically 48000.0 */ + float* sample_float = (float*)&sample_rate_int; + sample_rate = (int)*sample_float; + } else { /* default when not specified (most of the time) */ + sample_rate = 48000; + } + + + /* TRKP chunks (x16) */ + /* just seem to contain pan/vol stuff (0x7f/0x40), TRKP per track (sometimes +1 main track?) */ + /* there is channel layout bitmask at 0x0f (ex. 1ch = 0x04, 3ch = 0x07, 4ch = 0x33, 6ch = 0x3f), surely: + * FL 0x01, FR 0x02, FC = 0x04, BL = 0x08, BR = 0x10, BC = 0x20 */ + + start_offset = 0x800; + + /* DATA chunk */ + if (read_32bitBE(0x7f8, streamFile) != 0x44415441) // "DATA" + goto fail; + //if (read_32bitBE(0x7fc, streamFile) + start_offset != get_streamfile_size(streamFile)) + // goto fail; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = loop_end; + vgmstream->loop_start_sample = loop_start; + vgmstream->loop_end_sample = loop_end; + + vgmstream->coding_type = coding_MTA2; + vgmstream->layout_type = layout_none; + vgmstream->meta_type = meta_MTA2; + + /* open the file for reading */ + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} + +/* ****************************************************************************** */ + +/* MTA2 in containers */ +VGMSTREAM * init_vgmstream_mta2_container(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + off_t subfile_offset; + + + /* checks */ + /* .dbm: iPod metadata + mta2 with KCEJ blocks, .bgm: mta2 with KCEJ blocks (fake?) */ + if ( !check_extensions(streamFile,"dbm,bgm,mta2")) + goto fail; + + if (read_32bitBE(0x00,streamFile) == 0x444C424D) { /* "DLBM" */ + subfile_offset = 0x800; + } + else if (read_32bitBE(0x00,streamFile) == 0x00000010) { + subfile_offset = 0x00; + } + else { + goto fail; + } + /* subfile size is implicit in KCEJ blocks */ + + temp_streamFile = setup_mta2_streamfile(streamFile, subfile_offset, 1, "mta2"); + if (!temp_streamFile) goto fail; + + vgmstream = init_vgmstream_mta2(temp_streamFile); + close_streamfile(temp_streamFile); + + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mta2_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/mta2_streamfile.h new file mode 100644 index 000000000..c38d20b59 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/mta2_streamfile.h @@ -0,0 +1,154 @@ +#ifndef _MTA2_STREAMFILE_H_ +#define _MTA2_STREAMFILE_H_ +#include "../streamfile.h" + +typedef struct { + /* config */ + int big_endian; + uint32_t target_type; + off_t stream_offset; + size_t stream_size; + + /* state */ + off_t logical_offset; /* fake offset */ + off_t physical_offset; /* actual offset */ + size_t block_size; /* current size */ + size_t skip_size; /* size from block start to reach data */ + size_t data_size; /* usable size in a block */ + + size_t logical_size; +} mta2_io_data; + + +static size_t mta2_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, mta2_io_data* data) { + size_t total_read = 0; + uint32_t (*read_u32)(off_t,STREAMFILE*) = data->big_endian ? read_u32be : read_u32le; + + + + /* re-start when previous offset (can't map logical<>physical offsets) */ + if (data->logical_offset < 0 || offset < data->logical_offset) { + data->physical_offset = data->stream_offset; + data->logical_offset = 0x00; + data->data_size = 0; + } + + /* read blocks */ + while (length > 0) { + + /* ignore EOF */ + if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { + break; + } + + /* process new block */ + if (data->data_size == 0) { + uint32_t block_type, block_size, block_track; + + block_type = read_u32(data->physical_offset+0x00, streamfile); /* subtype and type */ + block_size = read_u32(data->physical_offset+0x04, streamfile); + //block_unk = read_u32(data->physical_offset+0x08, streamfile); /* usually 0 except for 0xF0 'end' block */ + block_track = read_u32(data->physical_offset+0x0c, streamfile); + + if (block_type != data->target_type || block_size == 0xFFFFFFFF) + break; + + data->block_size = block_size; + data->skip_size = 0x10; + data->data_size = block_size - data->skip_size; + /* no audio data (padding block), but write first (header) */ + if (block_track == 0 && data->logical_offset > 0) + data->data_size = 0; + } + + /* move to next block */ + if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size; + data->data_size = 0; + continue; + } + + /* read data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + +static size_t mta2_io_size(STREAMFILE *streamfile, mta2_io_data* data) { + uint8_t buf[1]; + + if (data->logical_size) + return data->logical_size; + + /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ + mta2_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles removing KCE Japan-style blocks in MTA2 streams + * (these blocks exist in most KCEJ games and aren't actually related to audio) */ +static STREAMFILE* setup_mta2_streamfile(STREAMFILE *streamFile, off_t stream_offset, int big_endian, const char* extension) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + mta2_io_data io_data = {0}; + size_t io_data_size = sizeof(mta2_io_data); + uint32_t (*read_u32)(off_t,STREAMFILE*) = big_endian ? read_u32be : read_u32le; + + + /* blocks must start with a 'new sub-stream' id */ + if (read_u32(stream_offset+0x00, streamFile) != 0x00000010) + goto fail; + + io_data.target_type = read_u32(stream_offset + 0x0c, streamFile); + io_data.stream_offset = stream_offset + 0x10; + io_data.stream_size = get_streamfile_size(streamFile) - io_data.stream_offset; + io_data.big_endian = big_endian; + io_data.logical_offset = -1; /* force phys offset reset */ + + /* setup subfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, mta2_io_read,mta2_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_buffer_streamfile(new_streamFile,0); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + if (extension) { + new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,extension); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + } + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +#endif /* _MTA2_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps2_mtaf.c b/Frameworks/vgmstream/vgmstream/src/meta/mtaf.c similarity index 65% rename from Frameworks/vgmstream/vgmstream/src/meta/ps2_mtaf.c rename to Frameworks/vgmstream/vgmstream/src/meta/mtaf.c index e760399bd..16f9087ff 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps2_mtaf.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/mtaf.c @@ -2,19 +2,17 @@ #include "../util.h" -/* MTAF - found in Metal Gear Solid 3: Snake Eater (Subsistence and HD too) */ -VGMSTREAM * init_vgmstream_ps2_mtaf(STREAMFILE *streamFile) { +/* MTAF - found in Metal Gear Solid 3: Snake Eater (PS2), Subsistence and HD too */ +VGMSTREAM * init_vgmstream_mtaf(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count; int32_t loop_start, loop_end; - /* check extension */ + /* checks */ if ( !check_extensions(streamFile,"mtaf")) goto fail; - - /* base header */ if (read_32bitBE(0x00, streamFile) != 0x4d544146) /* "MTAF" */ goto fail; /* 0x04(4): pseudo file size (close but smaller) */ @@ -34,7 +32,7 @@ VGMSTREAM * init_vgmstream_ps2_mtaf(STREAMFILE *streamFile) { loop_start = read_32bitLE(0x58, streamFile); loop_end = read_32bitLE(0x5c, streamFile); - loop_flag = (loop_start != loop_end); + loop_flag = read_32bitLE(0x70, streamFile) & 1; /* check loop points vs frame counts */ if (loop_start/0x100 != read_32bitLE(0x64, streamFile) || @@ -51,12 +49,9 @@ VGMSTREAM * init_vgmstream_ps2_mtaf(STREAMFILE *streamFile) { goto fail; /* 0x7fc: data size (without blocks in case of blocked layout) */ - /* without blocks it should start with 0x00000100 ("frame 1 from track 0") */ - //is_blocked = read_32bitLE(0x800,streamFile) != 0x00000100 && read_32bitLE(0x810,streamFile) == 0x00000100; - - start_offset = 0x800; + /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count, loop_flag); if (!vgmstream) goto fail; @@ -68,23 +63,11 @@ VGMSTREAM * init_vgmstream_ps2_mtaf(STREAMFILE *streamFile) { vgmstream->coding_type = coding_MTAF; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x110/2; /* kinda hacky for MTAF track layout */ - vgmstream->meta_type = meta_PS2_MTAF; + vgmstream->interleave_block_size = 0x110 / 2; /* kinda hacky for MTAF (stereo codec) track layout */ + vgmstream->meta_type = meta_MTAF; - - /* open the file for reading, in a specific way */ - { - int i; - char filename[PATH_LIMIT]; - - streamFile->get_name(streamFile,filename,sizeof(filename)); - for (i = 0; i < channel_count; i++) { - STREAMFILE * file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!file) goto fail; - vgmstream->ch[i].streamfile = file; - vgmstream->ch[i].channel_start_offset = vgmstream->ch[i].offset = start_offset + vgmstream->interleave_block_size*2*(i/2); - } - } + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; return vgmstream; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/mus_vc.c b/Frameworks/vgmstream/vgmstream/src/meta/mus_vc.c new file mode 100644 index 000000000..1159b5ced --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/mus_vc.c @@ -0,0 +1,79 @@ +#include "meta.h" +#include "../coding/coding.h" + +/* .MUS - Vicious Cycle games [Dinotopia: The Sunstone Odyssey (GC/Xbox), Robotech: Battlecry (PS2/Xbox)] */ +VGMSTREAM * init_vgmstream_mus_vc(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate; + int big_endian, type; + int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; + + + /* checks */ + if (!check_extensions(streamFile, "mus")) + goto fail; + + if (read_32bitBE(0x08,streamFile) != 0xBBBBBBBB && + read_32bitBE(0x14,streamFile) != 0xBBBBBBBB && + read_32bitBE(0x2c,streamFile) != 0xBEBEBEBE) + goto fail; + + big_endian = (read_32bitBE(0x00,streamFile) == 0xFBBFFBBF); + read_32bit = big_endian ? read_32bitBE : read_32bitLE; + + type = read_32bit(0x04, streamFile); + /* 0x08: pseudo size? */ + /* other fields may be chunk sizes and lesser stuff */ + /* 0x88: codec header */ + + channel_count = read_32bit(0x54,streamFile); /* assumed */ + if (channel_count != 1) goto fail; + sample_rate = read_32bit(0x58,streamFile); + loop_flag = 1; /* most files repeat except small jingles, but smaller ambient tracks also repeat */ + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_MUS_VC; + vgmstream->sample_rate = sample_rate; + + switch(type) { + case 0x01: + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_none; + vgmstream->num_samples = dsp_bytes_to_samples(read_32bit(0xB0,streamFile), vgmstream->channels); + vgmstream->loop_start_sample = 0; + vgmstream->loop_end_sample = vgmstream->num_samples; + + start_offset = 0xB8; + dsp_read_coefs_be(vgmstream,streamFile,0x88,0x00); + dsp_read_hist_be (vgmstream,streamFile,0xac,0x00); + break; + + case 0x02: + vgmstream->coding_type = coding_XBOX_IMA; + vgmstream->layout_type = layout_none; + vgmstream->num_samples = xbox_ima_bytes_to_samples(read_32bit(0x9a,streamFile), vgmstream->channels); + vgmstream->loop_start_sample = 0; + vgmstream->loop_end_sample = vgmstream->num_samples; + + start_offset = 0x9e; + break; + + default: + goto fail; + } + + read_string(vgmstream->stream_name,0x14, 0x34,streamFile); /* repeated at 0x64, size at 0x30/0x60 */ + + if (!vgmstream_open_stream(vgmstream,streamFile,start_offset)) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/naomi_adpcm.c b/Frameworks/vgmstream/vgmstream/src/meta/naomi_adpcm.c index 764f3a1b1..27acbeeeb 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/naomi_adpcm.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/naomi_adpcm.c @@ -27,9 +27,9 @@ VGMSTREAM * init_vgmstream_naomi_adpcm(STREAMFILE *streamFile) { if (!vgmstream) goto fail; vgmstream->sample_rate = 44100; - vgmstream->num_samples = aica_bytes_to_samples(data_size, channel_count); + vgmstream->num_samples = yamaha_bytes_to_samples(data_size, channel_count); - vgmstream->coding_type = coding_AICA_int; + vgmstream->coding_type = coding_YAMAHA_int; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = data_size / channel_count; vgmstream->meta_type = meta_NAOMI_ADPCM; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/naomi_spsd.c b/Frameworks/vgmstream/vgmstream/src/meta/naomi_spsd.c index f9b5e7682..442ad1171 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/naomi_spsd.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/naomi_spsd.c @@ -59,9 +59,9 @@ VGMSTREAM * init_vgmstream_naomi_spsd(STREAMFILE *streamFile) { break; case 0x03: /* standard */ - vgmstream->coding_type = coding_AICA_int; - vgmstream->num_samples = aica_bytes_to_samples(data_size,channel_count); - vgmstream->loop_start_sample = /*read_32bitLE(0x2c,streamFile) +*/ aica_bytes_to_samples(0x2000*channel_count,channel_count); + vgmstream->coding_type = coding_YAMAHA_int; + vgmstream->num_samples = yamaha_bytes_to_samples(data_size,channel_count); + vgmstream->loop_start_sample = /*read_32bitLE(0x2c,streamFile) +*/ yamaha_bytes_to_samples(0x2000*channel_count,channel_count); vgmstream->loop_end_sample = vgmstream->num_samples; break; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c b/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c index 010287e18..79f2b19b9 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ngc_dsp_std.c @@ -35,6 +35,8 @@ static int read_dsp_header_endian(struct dsp_header *header, off_t offset, STREA int i; uint8_t buf[0x4e]; + if (offset > get_streamfile_size(streamFile)) + return 1; if (read_streamfile(buf, offset, 0x4e, streamFile) != 0x4e) return 1; header->sample_count = get_32bit(buf+0x00); @@ -223,7 +225,7 @@ static VGMSTREAM * init_vgmstream_dsp_common(STREAMFILE *streamFile, dsp_meta *d if (dspm->fix_looping && vgmstream->loop_end_sample > vgmstream->num_samples) vgmstream->loop_end_sample = vgmstream->num_samples; - if (dspm->single_header) { + if (dspm->single_header == 2) { /* double the samples */ vgmstream->num_samples /= dspm->channel_count; vgmstream->loop_start_sample /= dspm->channel_count; vgmstream->loop_end_sample /= dspm->channel_count; @@ -270,14 +272,29 @@ VGMSTREAM * init_vgmstream_ngc_dsp_std(STREAMFILE *streamFile) { * out thoroughly, we're probably not dealing with a genuine mono DSP. * In many cases these will pass all the other checks, including the * predictor/scale check if the first byte is 0 */ + //todo maybe this meta should be after others, so they have a chance to detect >1ch .dsp { + int ko; struct dsp_header header2; - read_dsp_header(&header2, header_size, streamFile); - if (header.sample_count == header2.sample_count && - header.nibble_count == header2.nibble_count && - header.sample_rate == header2.sample_rate && - header.loop_flag == header2.loop_flag) { + /* ignore headers one after another */ + ko = read_dsp_header(&header2, header_size, streamFile); + if (!ko && + header.sample_count == header2.sample_count && + header.nibble_count == header2.nibble_count && + header.sample_rate == header2.sample_rate && + header.loop_flag == header2.loop_flag) { + goto fail; + } + + + /* ignore headers after interleave [Ultimate Board Collection (Wii)] */ + ko = read_dsp_header(&header2, 0x10000, streamFile); + if (!ko && + header.sample_count == header2.sample_count && + header.nibble_count == header2.nibble_count && + header.sample_rate == header2.sample_rate && + header.loop_flag == header2.loop_flag) { goto fail; } } @@ -517,14 +534,13 @@ VGMSTREAM * init_vgmstream_ngc_mpdsp(STREAMFILE *streamFile) { if (!check_extensions(streamFile, "mpdsp")) goto fail; - /* at 0x48 is extra data that could help differenciating these DSPs, but other games - * put similar stuff there, needs more checks (ex. Battallion Wars, Army Men) */ - //0x00005300 60A94000 64FF1200 00000000 00000000 00000000 + /* at 0x48 is extra data that could help differenciating these DSPs, but seems like + * memory garbage created by the encoder that other games also have */ /* 0x02(2): sample rate, 0x08+: channel sizes/loop offsets? */ dspm.channel_count = 2; dspm.max_channels = 2; - dspm.single_header = 1; + dspm.single_header = 2; dspm.header_offset = 0x00; dspm.header_spacing = 0x00; /* same header for both channels */ @@ -1196,3 +1212,37 @@ VGMSTREAM * init_vgmstream_dsp_adpcmx(STREAMFILE *streamFile) { fail: return NULL; } + +/* .ds2 - LucasArts wrapper [Star Wars: Bounty Hunter (GC)] */ +VGMSTREAM * init_vgmstream_dsp_ds2(STREAMFILE *streamFile) { + dsp_meta dspm = {0}; + size_t file_size, channel_offset; + + /* checks */ + /* .ds2: real extension, dsp: fake/renamed */ + if (!check_extensions(streamFile, "ds2,dsp")) + goto fail; + if (!(read_32bitBE(0x50,streamFile) == 0 && + read_32bitBE(0x54,streamFile) == 0 && + read_32bitBE(0x58,streamFile) == 0 && + read_32bitBE(0x5c,streamFile) != 0)) + goto fail; + file_size = get_streamfile_size(streamFile); + channel_offset = read_32bitBE(0x5c,streamFile); /* absolute offset to 2nd channel */ + if (channel_offset < file_size / 2 || channel_offset > file_size) /* just to make sure */ + goto fail; + + dspm.channel_count = 2; + dspm.max_channels = 2; + dspm.single_header = 1; + + dspm.header_offset = 0x00; + dspm.header_spacing = 0x00; + dspm.start_offset = 0x60; + dspm.interleave = channel_offset - dspm.start_offset; + + dspm.meta_type = meta_DSP_DS2; + return init_vgmstream_dsp_common(streamFile, &dspm); +fail: + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ngc_sck_dsp.c b/Frameworks/vgmstream/vgmstream/src/meta/ngc_sck_dsp.c index f281bace8..550c295dc 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ngc_sck_dsp.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ngc_sck_dsp.c @@ -1,98 +1,55 @@ #include "meta.h" -#include "../util.h" +#include "../coding/coding.h" -/* - SCK+DSP - 2009-08-25 - manakoAT : Scorpion King (NGC)... -*/ +//todo this was extracted from a .pak bigfile. Inside are headers then data (no extensions), +// but headers are VAGp in the PS2 version, so it would make more sense to extract pasting +// header+data together, or support as-is (.SCK is a fake extension). + +/* SCK+DSP - Scorpion King (GC) */ VGMSTREAM * init_vgmstream_ngc_sck_dsp(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - STREAMFILE * streamFileDSP = NULL; - char filename[PATH_LIMIT]; - char filenameDSP[PATH_LIMIT]; - - int i; - int channel_count; - int loop_flag; + STREAMFILE * streamHeader = NULL; + int channel_count, loop_flag; + size_t data_size; /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("sck",filename_extension(filename))) goto fail; - - - strcpy(filenameDSP,filename); - strcpy(filenameDSP+strlen(filenameDSP)-3,"dsp"); - - streamFileDSP = streamFile->open(streamFile,filenameDSP,STREAMFILE_DEFAULT_BUFFER_SIZE); - - if (read_32bitBE(0x5C,streamFile) != 0x60A94000) + if (!check_extensions(streamFile, "dsp")) goto fail; - if (!streamFile) goto fail; + streamHeader = open_streamfile_by_ext(streamFile, "sck"); + if (!streamHeader) goto fail; + + if (read_32bitBE(0x5C,streamHeader) != 0x60A94000) + goto fail; channel_count = 2; loop_flag = 0; + data_size = read_32bitBE(0x14,streamHeader); /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; - /* fill in the vital statistics */ - vgmstream->channels = channel_count; - vgmstream->sample_rate = read_32bitBE(0x18,streamFile); - vgmstream->num_samples=read_32bitBE(0x14,streamFile)/8/channel_count*14; + vgmstream->sample_rate = read_32bitBE(0x18,streamHeader); + vgmstream->num_samples = dsp_bytes_to_samples(data_size, channel_count); vgmstream->coding_type = coding_NGC_DSP; - - if(loop_flag) { - vgmstream->loop_start_sample = 0; - vgmstream->loop_end_sample = read_32bitBE(0x10,streamFile)/8/channel_count*14; - } - - if (channel_count == 1) { - vgmstream->layout_type = layout_none; - } else if (channel_count == 2) { - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size=read_32bitBE(0xC,streamFile); - } - - + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = read_32bitBE(0xC,streamHeader); + if (vgmstream->interleave_block_size > 0) + vgmstream->interleave_last_block_size = (data_size % (vgmstream->interleave_block_size * channel_count)) / channel_count; vgmstream->meta_type = meta_NGC_SCK_DSP; - /* open the file for reading */ - { - for (i=0;ich[i].streamfile = streamFile->open(streamFileDSP,filenameDSP,STREAMFILE_DEFAULT_BUFFER_SIZE); - vgmstream->ch[i].offset = 0; + dsp_read_coefs_be(vgmstream,streamHeader, 0x2c, 0x00); - if (!vgmstream->ch[i].streamfile) goto fail; - } - } - - - - if (vgmstream->coding_type == coding_NGC_DSP) { - int i; - for (i=0;i<16;i++) { - vgmstream->ch[0].adpcm_coef[i] = read_16bitBE(0x2C+i*2,streamFile); - } - if (vgmstream->channels == 2) { - for (i=0;i<16;i++) { - vgmstream->ch[1].adpcm_coef[i] = read_16bitBE(0x2C+i*2,streamFile); - } - } - } - - - close_streamfile(streamFileDSP); streamFileDSP=NULL; + if (!vgmstream_open_stream(vgmstream,streamFile,0x00)) + goto fail; + close_streamfile(streamHeader); return vgmstream; - /* clean up anything we may have opened */ fail: - if (streamFileDSP) close_streamfile(streamFileDSP); - if (vgmstream) close_vgmstream(vgmstream); + close_streamfile(streamHeader); + close_vgmstream(vgmstream); return NULL; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps2_npsf.c b/Frameworks/vgmstream/vgmstream/src/meta/nps.c similarity index 80% rename from Frameworks/vgmstream/vgmstream/src/meta/ps2_npsf.c rename to Frameworks/vgmstream/vgmstream/src/meta/nps.c index 76ec20b2f..bc89cd818 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps2_npsf.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/nps.c @@ -1,13 +1,15 @@ #include "meta.h" #include "../coding/coding.h" -/* NPFS - found in Namco PS2/PSP games (Tekken 5, Ace Combat 5, Yumeria, Venus & Braves, Ridge Racer PSP) */ -VGMSTREAM * init_vgmstream_ps2_npsf(STREAMFILE *streamFile) { +/* NPFS - found in Namco PS2/PSP games [Tekken 5 (PS2), Venus & Braves (PS2), Ridge Racer (PSP)] */ +VGMSTREAM * init_vgmstream_nps(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset; int loop_flag, channel_count; - /* check extension, case insensitive (should be .nps as per Venus & Braves data files) */ + /* checks */ + /* .nps: referenced extension (ex. Venus & Braves data files) + * .npsf: header id (Namco Production Sound File?) */ if ( !check_extensions(streamFile,"nps,npsf")) goto fail; @@ -16,7 +18,8 @@ VGMSTREAM * init_vgmstream_ps2_npsf(STREAMFILE *streamFile) { loop_flag = (read_32bitLE(0x14,streamFile) != 0xFFFFFFFF); channel_count = read_32bitLE(0x0C,streamFile); - + start_offset = (off_t)read_32bitLE(0x10,streamFile); + /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(channel_count,loop_flag); if (!vgmstream) goto fail; @@ -32,13 +35,9 @@ VGMSTREAM * init_vgmstream_ps2_npsf(STREAMFILE *streamFile) { vgmstream->coding_type = coding_PSX; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = read_32bitLE(0x04,streamFile) / 2; - vgmstream->meta_type = meta_PS2_NPSF; + vgmstream->meta_type = meta_NPS; read_string(vgmstream->stream_name,STREAM_NAME_SIZE, 0x34,streamFile); - start_offset = (off_t)read_32bitLE(0x10,streamFile); - - - /* open the file for reading */ if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) goto fail; return vgmstream; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/nxap.c b/Frameworks/vgmstream/vgmstream/src/meta/nxap.c index 813ec164e..d87be6638 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/nxap.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/nxap.c @@ -35,7 +35,7 @@ VGMSTREAM * init_vgmstream_nxap(STREAMFILE *streamFile) { //vgmstream->loop_end_sample = vgmstream->loop_start_sample + vgmstream->loop_end_sample; vgmstream->meta_type = meta_NXAP; - vgmstream->coding_type = coding_YAMAHA_NXAP; + vgmstream->coding_type = coding_NXAP; vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x40; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ogg_opus.c b/Frameworks/vgmstream/vgmstream/src/meta/ogg_opus.c index 41a5ca736..dd1d34096 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ogg_opus.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ogg_opus.c @@ -111,6 +111,7 @@ VGMSTREAM * init_vgmstream_ogg_opus(STREAMFILE *streamFile) { if (!vgmstream->codec_data) goto fail; vgmstream->coding_type = coding_FFmpeg; vgmstream->layout_type = layout_none; + vgmstream->channel_layout = ffmpeg_get_channel_layout(vgmstream->codec_data); /* FFmpeg+libopus handles skip samples ok, FFmpeg+opus doesn't */ } #else diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis.c b/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis.c index b9342421b..9ad81942b 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ogg_vorbis.c @@ -275,6 +275,18 @@ static void lse_ff_ogg_decryption_callback(void *ptr, size_t size, size_t nmemb, } } +static const uint32_t xiph_mappings[] = { + 0, + mapping_MONO, + mapping_STEREO, + mapping_2POINT1_xiph, + mapping_QUAD, + mapping_5POINT0_xiph, + mapping_5POINT1, + mapping_7POINT0, + mapping_7POINT1, +}; + /* Ogg Vorbis, by way of libvorbisfile; may contain loop comments */ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { @@ -298,8 +310,9 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { * .adx: KID [Remember11 (PC)] * .rof: The Rhythm of Fighters (Mobile) * .acm: Planescape Torment Enhanced Edition (PC) - * .sod: Zone 4 (PC) */ - if (check_extensions(streamFile,"ogg,logg,adx,rof,acm,sod")) { + * .sod: Zone 4 (PC) + * .aif/laif/aif-Loop: Psychonauts (PC) raw extractions (named) */ + if (check_extensions(streamFile,"ogg,logg,adx,rof,acm,sod,aif,laif,aif-Loop")) { is_ogg = 1; } else if (check_extensions(streamFile,"um3")) { is_um3 = 1; @@ -369,6 +382,7 @@ VGMSTREAM * init_vgmstream_ogg_vorbis(STREAMFILE *streamFile) { ovmi.xor_value = read_32bitBE(0x00,streamFile); ovmi.decryption_callback = sngw_ogg_decryption_callback; } + ovmi.disable_reordering = 1; /* must be an MT Framework thing */ ovmi.meta_type = meta_OGG_encrypted; } @@ -518,6 +532,9 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb data->bitstream = OGG_DEFAULT_BITSTREAM; vi = ov_info(ovf,OGG_DEFAULT_BITSTREAM); + /* other settings */ + data->disable_reordering = ovmi->disable_reordering; + /* search for loop comments */ { int i; @@ -601,6 +618,13 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb } } + /* Hatsune Miku Project DIVA games, though only 'Arcade Future Tone' has >4ch files + * ENCODER tag is common but ogg_vorbis_encode looks unique enough + * (arcade ends with "2010-11-26" while consoles have "2011-02-07" */ + if (strstr(user_comment, "ENCODER=ogg_vorbis_encode/") == user_comment) { + data->disable_reordering = 1; + } + ;VGM_LOG("OGG: user_comment=%s\n", user_comment); } } @@ -634,6 +658,10 @@ VGMSTREAM * init_vgmstream_ogg_vorbis_callbacks(STREAMFILE *streamFile, ov_callb vgmstream->layout_type = layout_none; vgmstream->meta_type = ovmi->meta_type; + if (vgmstream->channels <= 8) { + vgmstream->channel_layout = xiph_mappings[vgmstream->channels]; + } + return vgmstream; fail: diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps2_ads.c b/Frameworks/vgmstream/vgmstream/src/meta/ps2_ads.c index fc00f4679..75559c6b1 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps2_ads.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ps2_ads.c @@ -35,7 +35,7 @@ VGMSTREAM * init_vgmstream_ps2_ads(STREAMFILE *streamFile) { { codec = read_32bitLE(0x08,streamFile); sample_rate = read_32bitLE(0x0C,streamFile); - channel_count = read_32bitLE(0x10,streamFile); /* up to 4 [Eve of Extinction (PS2)]*/ + channel_count = read_32bitLE(0x10,streamFile); /* up to 4 [Eve of Extinction (PS2)] */ interleave = read_32bitLE(0x14,streamFile); /* set even when mono */ @@ -150,7 +150,7 @@ VGMSTREAM * init_vgmstream_ps2_ads(STREAMFILE *streamFile) { loop_start_sample = loop_start / 2 / channel_count; is_loop_samples = 1; } - else if ((loop_start % 0x800 == 0) && loop_start > 0) {/* sector-aligned, min/0 is 0x800 */ + else if ((loop_start % 0x800 == 0) && loop_start > 0) { /* sector-aligned, min/0 is 0x800 */ /* cavia games: loop_start is offset [Drakengard 1/2, GITS: Stand Alone Complex] */ /* offset is absolute from the "cavia stream format" container that adjusts ADS start */ loop_flag = 1; @@ -192,12 +192,18 @@ VGMSTREAM * init_vgmstream_ps2_ads(STREAMFILE *streamFile) { loop_start_offset = loop_start * 0x20; loop_end_offset = loop_end * 0x20; } - else if (loop_end <= body_size / 0x20 && coding_type == coding_PSX) { /* close to body_size */ + else if (loop_end <= body_size / 0x20 && coding_type == coding_PSX) { /* various games: loops is address * 0x20 [Fire Pro Wrestling Returns, A.C.E. - Another Century's Episode] */ loop_flag = 1; loop_start_offset = loop_start * 0x20; loop_end_offset = loop_end * 0x20; } + else if (loop_end <= body_size / 0x10 && coding_type == coding_PSX + && (read_32bitBE(0x28 + loop_end*0x10 + 0x10 + 0x00, streamFile) == 0x00077777 || + read_32bitBE(0x28 + loop_end*0x10 + 0x20 + 0x00, streamFile) == 0x00077777)) { + /* not-quite-looping sfx, ending with a "non-looping PS-ADPCM end frame" [Kono Aozora ni Yakusoku, Chanter] */ + loop_flag = 0; + } else if ((loop_end > body_size / 0x20 && coding_type == coding_PSX) || (loop_end > body_size / 0x70 && coding_type == coding_PCM16LE)) { /* various games: loops in samples [Eve of Extinction, Culdcept, WWE Smackdown! 3] */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps2_ikm.c b/Frameworks/vgmstream/vgmstream/src/meta/ps2_ikm.c deleted file mode 100644 index 2b16f156a..000000000 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps2_ikm.c +++ /dev/null @@ -1,63 +0,0 @@ -#include "meta.h" -#include "../util.h" - -/* IKM (found in Zwei!) */ -VGMSTREAM * init_vgmstream_ikm(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - char filename[PATH_LIMIT]; - off_t start_offset; - int loop_flag = 0; - int channel_count; - - /* check extension, case insensitive */ - streamFile->get_name(streamFile,filename,sizeof(filename)); - if (strcasecmp("ikm",filename_extension(filename))) goto fail; - - /* check header */ - if ((read_32bitBE(0x00,streamFile) != 0x494B4D00) && /* "IKM\0" */ - (read_32bitBE(0x40,streamFile) != 0x41535400)) /* "AST\0" */ - goto fail; - - loop_flag = (read_32bitLE(0x14,streamFile)!=0); /* Not sure */ - channel_count = read_32bitLE(0x50,streamFile); - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - /* fill in the vital statistics */ - start_offset = 0x800; - vgmstream->channels = channel_count; - vgmstream->sample_rate = read_32bitLE(0x44,streamFile); - vgmstream->coding_type = coding_PSX; - vgmstream->num_samples = (read_32bitLE(0x4C,streamFile)-start_offset)/16/channel_count*28; - if (loop_flag) { - vgmstream->loop_start_sample = read_32bitLE(0x14,streamFile); - vgmstream->loop_end_sample = read_32bitLE(0x18,streamFile); - } - - vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = 0x10; - vgmstream->meta_type = meta_IKM; - - /* open the file for reading */ - { - int i; - STREAMFILE * file; - file = streamFile->open(streamFile,filename,STREAMFILE_DEFAULT_BUFFER_SIZE); - if (!file) goto fail; - for (i=0;ich[i].streamfile = file; - vgmstream->ch[i].channel_start_offset= - vgmstream->ch[i].offset=start_offset+ - vgmstream->interleave_block_size*i; - } - } - - return vgmstream; - - /* clean up anything we may have opened */ -fail: - if (vgmstream) close_vgmstream(vgmstream); - return NULL; -} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ps3_mta2.c b/Frameworks/vgmstream/vgmstream/src/meta/ps3_mta2.c deleted file mode 100644 index 83825f812..000000000 --- a/Frameworks/vgmstream/vgmstream/src/meta/ps3_mta2.c +++ /dev/null @@ -1,104 +0,0 @@ -#include "meta.h" -#include "../util.h" - - -/* MTA2 - found in Metal Gear Solid 4 */ -VGMSTREAM * init_vgmstream_ps3_mta2(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; - off_t header_offset, start_offset; - int loop_flag, channel_count, sample_rate; //block_offset; - int32_t loop_start, loop_end; - - - /* check extension */ - /* .mta2: normal file, .bgm: mta2 with block layout, .dbm: iPod metadata + block layout mta2 */ - if ( !check_extensions(streamFile,"mta2,bgm,dbm")) - goto fail; - - /* base header (everything is very similar to MGS3's MTAF but BE) */ - if (read_32bitBE(0x00,streamFile) == 0x4d544132) { /* "MTA2" (.mta) */ - //block_offset = 0; - header_offset = 0x00; - } else if (read_32bitBE(0x20,streamFile) == 0x4d544132) { /* "MTA2" @ 0x20 (.bgm) */ - //block_offset = 0x10; - header_offset = 0x20; - } else if (read_32bitBE(0x00, streamFile) == 0x444C424D - && read_32bitBE(0x820,streamFile) == 0x4d544132) { /* "DLBM" + "MTA2" @ 0x820 (.dbm) */ - //block_offset = 0x810; - header_offset = 0x820; - } else { - goto fail; - } - /* 0x04(4): file size -4-4 (not including block headers in case of block layout) */ - /* 0x08(4): version? (1), 0x0c(52): null */ - - - /* HEAD chunk */ - if (read_32bitBE(header_offset+0x40, streamFile) != 0x48454144) /* "HEAD" */ - goto fail; - if (read_32bitBE(header_offset+0x44, streamFile) != 0xB0) /* HEAD size */ - goto fail; - - - - /* 0x48(4): null, 0x4c: ? (0x10), 0x50(4): 0x7F (vol?), 0x54(2): 0x40 (pan?) */ - channel_count = read_16bitBE(header_offset+0x56, streamFile); /* counting all tracks */ - /* 0x60(4): full block size (0x110 * channels), indirectly channels_per_track = channels / (block_size / 0x110) */ - /* 0x80 .. 0xf8: null */ - - loop_start = read_32bitBE(header_offset+0x58, streamFile); - loop_end = read_32bitBE(header_offset+0x5c, streamFile); - loop_flag = (loop_start != loop_end); /* also flag possibly @ 0x73 */ -#if 0 - /* those values look like some kind of loop offsets */ - if (loop_start/0x100 != read_32bitBE(header_offset+0x68, streamFile) || - loop_end /0x100 != read_32bitBE(header_offset+0x6C, streamFile) ) { - VGM_LOG("MTA2: wrong loop points\n"); - goto fail; - } -#endif - - sample_rate = read_32bitBE(header_offset+0x7c, streamFile); - if (sample_rate) { /* sample rate in 32b float (WHY?) typically 48000.0 */ - float sample_float; - memcpy(&sample_float, &sample_rate, 4); - sample_rate = (int)sample_float; - } else { /* default when not specified (most of the time) */ - sample_rate = 48000; - } - - - /* TRKP chunks (x16) */ - /* just seem to contain pan/vol stuff (0x7f/0x40), TRKP per track (sometimes +1 main track?) */ - /* there is channel layout bitmask @ 0x0f (ex. 1ch = 0x04, 3ch = 0x07, 4ch = 0x33, 6ch = 0x3f), surely: - * FRONT_L = 0x01, FRONT_R = 0x02, FRONT_M = 0x04, BACK_L = 0x08, BACK_R = 0x10, BACK_M = 0x20 */ - - /* DATA chunk */ - if (read_32bitBE(header_offset+0x7f8, streamFile) != 0x44415441) // "DATA" - goto fail; - /* 0x7fc: data size (without blocks in case of blocked layout) */ - - start_offset = header_offset + 0x800; - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = loop_end; - vgmstream->loop_start_sample = loop_start; - vgmstream->loop_end_sample = loop_end; - - vgmstream->coding_type = coding_MTA2; - vgmstream->layout_type = layout_none; - vgmstream->meta_type = meta_PS3_MTA2; - - /* open the file for reading */ - if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) - goto fail; - return vgmstream; - -fail: - close_vgmstream(vgmstream); - return NULL; -} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/riff.c b/Frameworks/vgmstream/vgmstream/src/meta/riff.c index 6830764a6..6006785ab 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/riff.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/riff.c @@ -87,9 +87,14 @@ typedef struct { int channel_count; uint32_t block_size; int bps; + off_t extra_size; + uint32_t channel_layout; int coding_type; int interleave; + + int is_at3; + int is_at9; } riff_fmt_chunk; static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk, riff_fmt_chunk * fmt, int mwv) { @@ -99,18 +104,35 @@ static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk fmt->offset = current_chunk; fmt->size = read_32bit(current_chunk+0x04,streamFile); - fmt->codec = (uint16_t)read_16bit(current_chunk+0x08,streamFile); - fmt->sample_rate = read_32bit(current_chunk+0x0c,streamFile); - fmt->channel_count = read_16bit(current_chunk+0x0a,streamFile); - fmt->block_size = read_16bit(current_chunk+0x14,streamFile); - fmt->bps = read_16bit(current_chunk+0x16,streamFile); - fmt->interleave = 0; + /* WAVEFORMAT */ + fmt->codec = (uint16_t)read_16bit(current_chunk+0x08,streamFile); + fmt->channel_count = read_16bit(current_chunk+0x0a,streamFile); + fmt->sample_rate = read_32bit(current_chunk+0x0c,streamFile); + //fmt->avg_bps = read_32bit(current_chunk+0x10,streamFile); + fmt->block_size = read_16bit(current_chunk+0x14,streamFile); + fmt->bps = read_16bit(current_chunk+0x16,streamFile); + /* WAVEFORMATEX */ + if (fmt->size >= 0x10) { + fmt->extra_size = read_16bit(current_chunk+0x18,streamFile); + /* 0x1a+ depends on codec (ex. coef table for MSADPCM, samples_per_frame in MS-IMA, etc) */ + } + /* WAVEFORMATEXTENSIBLE */ + if (fmt->codec == 0xFFFE && fmt->extra_size >= 0x16) { + //fmt->extra_samples = read_16bit(current_chunk+0x1a,streamFile); /* valid_bits_per_sample or samples_per_block */ + fmt->channel_layout = read_32bit(current_chunk+0x1c,streamFile); + /* 0x10 guid at 0x20 */ + + /* happens in .at3/at9, may be a bug in their encoder b/c MS's defs set mono as FC */ + if (fmt->channel_count == 1 && fmt->channel_layout == speaker_FL) { /* other channels are fine */ + fmt->channel_layout = speaker_FC; + } + } switch (fmt->codec) { case 0x00: /* Yamaha ADPCM (raw) [Headhunter (DC), Bomber hehhe (DC)] (unofficial) */ if (fmt->bps != 4) goto fail; if (fmt->block_size != 0x02*fmt->channel_count) goto fail; - fmt->coding_type = coding_AICA_int; + fmt->coding_type = coding_YAMAHA_int; fmt->interleave = 0x01; break; @@ -129,7 +151,7 @@ static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk } break; - case 0x02: /* MS ADPCM */ + case 0x02: /* MSADPCM */ if (fmt->bps == 4) { fmt->coding_type = coding_MSADPCM; } @@ -141,14 +163,14 @@ static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk } break; - case 0x11: /* MS IMA ADPCM [Layton Brothers: Mystery Room (iOS/Android)] */ + case 0x11: /* MS-IMA ADPCM [Layton Brothers: Mystery Room (iOS/Android)] */ if (fmt->bps != 4) goto fail; fmt->coding_type = coding_MS_IMA; break; case 0x20: /* Yamaha ADPCM (raw) [Takuyo/Dynamix/etc DC games] */ if (fmt->bps != 4) goto fail; - fmt->coding_type = coding_AICA; + fmt->coding_type = coding_YAMAHA; break; case 0x69: /* XBOX IMA ADPCM [Dynasty Warriors 5 (Xbox)] */ @@ -186,39 +208,56 @@ static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk case 0x270: /* ATRAC3 */ #ifdef VGM_USE_FFMPEG fmt->coding_type = coding_FFmpeg; + fmt->is_at3 = 1; break; #else goto fail; #endif - case 0xFFFE: /* WAVEFORMATEXTENSIBLE */ + case 0xFFFE: { /* WAVEFORMATEXTENSIBLE (see ksmedia.h for known GUIDs) */ + uint32_t guid1 = (uint32_t)read_32bit (current_chunk+0x20,streamFile); + uint32_t guid2 = ((uint16_t)read_16bit (current_chunk+0x24,streamFile) << 16u) | + ((uint16_t)read_16bit (current_chunk+0x26,streamFile)); + uint32_t guid3 = (uint32_t)read_32bitBE(current_chunk+0x28,streamFile); + uint32_t guid4 = (uint32_t)read_32bitBE(current_chunk+0x2c,streamFile); + //;VGM_LOG("RIFF: guid %08x %08x %08x %08x\n", guid1, guid2, guid3, guid4); - /* ATRAC3plus GUID (0xBFAA23E9 58CB7144 A119FFFA 01E4CE62) */ - if (read_32bit(current_chunk+0x20,streamFile) == 0xE923AABF && - read_16bit(current_chunk+0x24,streamFile) == (int16_t)0xCB58 && - read_16bit(current_chunk+0x26,streamFile) == 0x4471 && - read_32bitLE(current_chunk+0x28,streamFile) == 0xFAFF19A1 && - read_32bitLE(current_chunk+0x2C,streamFile) == 0x62CEE401) { + /* PCM GUID (0x00000001,0000,0010,80,00,00,AA,00,38,9B,71) */ + if (guid1 == 0x00000001 && guid2 == 0x00000010 && guid3 == 0x800000AA && guid4 == 0x00389B71) { + switch (fmt->bps) { + case 16: + fmt->coding_type = big_endian ? coding_PCM16BE : coding_PCM16LE; + fmt->interleave = 0x02; + break; + default: + goto fail; + } + break; + } + + /* ATRAC3plus GUID (0xE923AABF,CB58,4471,A1,19,FF,FA,01,E4,CE,62) */ + if (guid1 == 0xE923AABF && guid2 == 0xCB584471 && guid3 == 0xA119FFFA && guid4 == 0x01E4CE62) { #ifdef VGM_USE_MAIATRAC3PLUS uint16_t bztmp = read_16bit(current_chunk+0x32,streamFile); bztmp = (bztmp >> 8) | (bztmp << 8); fmt->coding_type = coding_AT3plus; - fmt->block_size = (bztmp & 0x3FF) * 8 + 8; //should match fmt->block_size + fmt->block_size = (bztmp & 0x3FF) * 8 + 8; /* should match fmt->block_size */ + fmt->is_at3 = 1; + break; #elif defined(VGM_USE_FFMPEG) fmt->coding_type = coding_FFmpeg; + fmt->is_at3 = 1; break; #else goto fail; #endif } - /* ATRAC9 GUID 47E142D2-36BA-4d8d-88FC-61654F8C836C (D242E147 BA368D4D 88FC6165 4F8C836C) */ - if (read_32bitBE(current_chunk+0x20,streamFile) == 0xD242E147 && - read_32bitBE(current_chunk+0x24,streamFile) == 0xBA368D4D && - read_32bitBE(current_chunk+0x28,streamFile) == 0x88FC6165 && - read_32bitBE(current_chunk+0x2c,streamFile) == 0x4F8C836C) { + /* ATRAC9 GUID (0x47E142D2,36BA,4D8D,88,FC,61,65,4F,8C,83,6C) */ + if (guid1 == 0x47E142D2 && guid2 == 0x36BA4D8D && guid3 == 0x88FC6165 && guid4 == 0x4F8C836C) { #ifdef VGM_USE_ATRAC9 fmt->coding_type = coding_ATRAC9; + fmt->is_at9 = 1; break; #else goto fail; @@ -226,15 +265,16 @@ static int read_fmt(int big_endian, STREAMFILE * streamFile, off_t current_chunk } goto fail; /* unknown GUID */ + } default: goto fail; } - return 0; + return 1; fail: - return -1; + return 0; } static int is_ue4_msadpcm(VGMSTREAM* vgmstream, STREAMFILE* streamFile, riff_fmt_chunk* fmt, int fact_sample_count, off_t start_offset); @@ -261,8 +301,6 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { int mwv = 0; /* Level-5 .mwv (Dragon Quest VIII, Rogue Galaxy) */ off_t mwv_pflt_offset = -1; off_t mwv_ctrl_offset = -1; - int at3 = 0; /* Sony ATRAC3 / ATRAC3plus */ - int at9 = 0; /* Sony ATRAC9 */ /* check extension */ @@ -282,22 +320,18 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { * .wd: Genma Onimusha (Xbox) voices * (extensionless): Myst III (Xbox) * .sbv: Spongebob Squarepants - The Movie (PC) - * .wvx: Godzilla - Destroy All Monsters Melee (Xbox) */ - if ( check_extensions(streamFile, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv") ) { + * .wvx: Godzilla - Destroy All Monsters Melee (Xbox) + * .at3: standard ATRAC3 + * .rws: Climax games (Silent Hill Origins PSP, Oblivion PSP) ATRAC3 + * .aud: EA Replay ATRAC3 + * .at9: standard ATRAC9 + */ + if ( check_extensions(streamFile, "wav,lwav,xwav,da,dax,cd,med,snd,adx,adp,xss,xsew,adpcm,adw,wd,,sbv,wvx,at3,rws,aud,at9") ) { ; } else if ( check_extensions(streamFile, "mwv") ) { mwv = 1; } - /* .at3: standard - * .rws: Climax games (Silent Hill Origins PSP, Oblivion PSP) - * .aud: EA Replay */ - else if ( check_extensions(streamFile, "at3,rws,aud") ) { - at3 = 1; - } - else if ( check_extensions(streamFile, "at9") ) { - at9 = 1; - } else { goto fail; } @@ -330,27 +364,21 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { /* read through chunks to verify format and find metadata */ { - off_t current_chunk = 0xc; /* start with first chunk */ + off_t current_chunk = 0x0c; /* start with first chunk */ while (current_chunk < file_size && current_chunk < riff_size+8) { - uint32_t chunk_type = read_32bitBE(current_chunk,streamFile); - size_t chunk_size = read_32bitLE(current_chunk+4,streamFile); + uint32_t chunk_id = read_32bitBE(current_chunk + 0x00,streamFile); /* FOURCC */ + size_t chunk_size = read_32bitLE(current_chunk + 0x04,streamFile); - if (fmt.codec == 0x6771 && chunk_type == 0x64617461) /* Liar-soft again */ - chunk_size += (chunk_size%2) ? 0x01 : 0x00; + if (current_chunk + 0x08 + chunk_size > file_size) + goto fail; - if (current_chunk+0x08+chunk_size > file_size) goto fail; - - switch(chunk_type) { + switch(chunk_id) { case 0x666d7420: /* "fmt " */ if (FormatChunkFound) goto fail; /* only one per file */ FormatChunkFound = 1; - if (-1 == read_fmt(0, /* big endian == false*/ - streamFile, - current_chunk, - &fmt, - mwv)) + if (!read_fmt(0, streamFile, current_chunk, &fmt, mwv)) goto fail; /* some Dreamcast/Naomi games again [Headhunter (DC), Bomber hehhe (DC)] */ @@ -367,7 +395,6 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { break; case 0x4C495354: /* "LIST" */ - /* what lurks within?? */ switch (read_32bitBE(current_chunk+0x08, streamFile)) { case 0x6164746C: /* "adtl" */ /* yay, atdl is its own little world */ @@ -417,10 +444,10 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { fact_sample_count = read_32bitLE(current_chunk+0x08, streamFile); } else if (chunk_size == 0x10 && read_32bitBE(current_chunk+0x08+0x04, streamFile) == 0x4C794E20) { /* "LyN " */ goto fail; /* parsed elsewhere */ - } else if ((at3 || at9) && chunk_size == 0x08) { + } else if ((fmt.is_at3 || fmt.is_at9) && chunk_size == 0x08) { fact_sample_count = read_32bitLE(current_chunk+0x08, streamFile); fact_sample_skip = read_32bitLE(current_chunk+0x0c, streamFile); - } else if ((at3 || at9) && chunk_size == 0x0c) { + } else if ((fmt.is_at3 || fmt.is_at9) && chunk_size == 0x0c) { fact_sample_count = read_32bitLE(current_chunk+0x08, streamFile); fact_sample_skip = read_32bitLE(current_chunk+0x10, streamFile); } @@ -430,6 +457,7 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { if (!mwv) break; /* ignore if not in an mwv */ mwv_pflt_offset = current_chunk; /* predictor filters */ break; + case 0x6374726c: /* "ctrl" (.mwv extension) */ if (!mwv) break; loop_flag = read_32bitLE(current_chunk+0x08, streamFile); @@ -457,7 +485,13 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { break; } - current_chunk += 8+chunk_size; + /* chunks are even-sized with padding byte (for 16b reads) as per spec (normally + * pre-adjusted except for a few like Liar-soft's), at end may not have padding though + * (done *after* chunk parsing since size without padding is needed) */ + if (chunk_size % 0x02 && current_chunk + 0x08 + chunk_size+0x01 <= file_size) + chunk_size += 0x01; + + current_chunk += 0x08 + chunk_size; } } @@ -481,6 +515,10 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { read_32bitBE(start_offset+0x3c, streamFile) == 0xFFFFFFFF) goto fail; + /* ignore Gitaroo Man Live! (PSP) multi-RIFF (to allow chunked TXTH) */ + if (fmt.is_at3 && get_streamfile_size(streamFile) > 0x2800 && read_32bitBE(0x2800, streamFile) == 0x52494646) { /* "RIFF" */ + goto fail; + } #ifdef VGM_USE_VORBIS /* special case using init_vgmstream_ogg_vorbis */ @@ -495,6 +533,7 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { if (!vgmstream) goto fail; vgmstream->sample_rate = fmt.sample_rate; + vgmstream->channel_layout = fmt.channel_layout; /* init, samples */ switch (fmt.coding_type) { @@ -544,9 +583,9 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { vgmstream->num_samples = fact_sample_count; break; - case coding_AICA: - case coding_AICA_int: - vgmstream->num_samples = aica_bytes_to_samples(data_size, fmt.channel_count); + case coding_YAMAHA: + case coding_YAMAHA_int: + vgmstream->num_samples = yamaha_bytes_to_samples(data_size, fmt.channel_count); break; case coding_XBOX_IMA: @@ -567,13 +606,28 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { vgmstream->num_samples = ffmpeg_data->totalSamples; /* fact_sample_count */ - if (at3) { + if (fmt.is_at3) { /* the encoder introduces some garbage (not always silent) samples to skip before the stream */ /* manually set skip_samples if FFmpeg didn't do it */ if (ffmpeg_data->skipSamples <= 0) { ffmpeg_set_skip_samples(ffmpeg_data, fact_sample_skip); } + /* LFE channel should be reordered on decode, but FFmpeg doesn't do it automatically: + * - 6ch: FL FR FC BL BR LFE > FL FR FC LFE BL BR + * - 8ch: FL FR FC BL BR SL SR LFE > FL FR FC LFE BL BR SL SR + * (ATRAC3Plus only, 5/7ch can't be encoded) */ + if (ffmpeg_data->channels == 6) { + /* LFE BR BL > LFE BL BR > same */ + int channel_remap[] = { 0, 1, 2, 5, 5, 5, }; + ffmpeg_set_channel_remapping(ffmpeg_data, channel_remap); + } + else if (ffmpeg_data->channels == 8) { + /* LFE BR SL SR BL > LFE BL SL SR BR > LFE BL BR SR SL > LFE BL BR SL SR > same */ + int channel_remap[] = { 0, 1, 2, 7, 7, 7, 7, 7}; + ffmpeg_set_channel_remapping(ffmpeg_data, channel_remap); + } + /* RIFF loop/sample values are absolute (with skip samples), adjust */ if (loop_flag) { loop_start_smpl -= (int32_t)ffmpeg_data->skipSamples; @@ -624,7 +678,7 @@ VGMSTREAM * init_vgmstream_riff(STREAMFILE *streamFile) { switch (fmt.coding_type) { case coding_MSADPCM: case coding_MS_IMA: - case coding_AICA: + case coding_YAMAHA: case coding_XBOX_IMA: case coding_IMA: #ifdef VGM_USE_FFMPEG @@ -803,11 +857,7 @@ VGMSTREAM * init_vgmstream_rifx(STREAMFILE *streamFile) { if (FormatChunkFound) goto fail; FormatChunkFound = 1; - if (-1 == read_fmt(1, /* big endian == true */ - streamFile, - current_chunk, - &fmt, - 0)) /* mwv == false */ + if (!read_fmt(1, streamFile, current_chunk, &fmt, 0)) goto fail; break; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sfh.c b/Frameworks/vgmstream/vgmstream/src/meta/sfh.c new file mode 100644 index 000000000..b22a2205f --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/sfh.c @@ -0,0 +1,46 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "sfh_streamfile.h" + + +/* .SFH - Capcom wrapper [Devil May Cry 4 Demo (PS3), Jojo's Bizarre Adventure HD (PS3)] */ +VGMSTREAM * init_vgmstream_sfh(STREAMFILE *streamFile) { + VGMSTREAM *vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + uint32_t version; + size_t clean_size, block_size; + + /* check extensions */ + if ( !check_extensions(streamFile,"at3")) + goto fail; + + if (read_32bitBE(0x00, streamFile) != 0x00534648) /* "\0SFH" */ + goto fail; + if (read_32bitBE(0x10, streamFile) != 0x52494646) /* "RIFF" */ + goto fail; + + /* mini header */ + version = read_32bitBE(0x04,streamFile); + clean_size = read_32bitBE(0x08,streamFile); /* there is padding data at the end */ + /* 0x0c: always 0 */ + + switch(version) { + case 0x00010000: block_size = 0x10010; break; /* DMC4 Demo (not retail) */ + case 0x00010001: block_size = 0x20000; break; /* Jojo */ + default: goto fail; + } + + temp_streamFile = setup_sfh_streamfile(streamFile, 0x00, block_size, clean_size, "at3"); + if (!temp_streamFile) goto fail; + + vgmstream = init_vgmstream_riff(temp_streamFile); + if (!vgmstream) goto fail; + + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sfh_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/sfh_streamfile.h new file mode 100644 index 000000000..954df1a0d --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/sfh_streamfile.h @@ -0,0 +1,133 @@ +#ifndef _SFH_STREAMFILE_H_ +#define _SFH_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + /* config */ + off_t stream_offset; + size_t stream_size; + + /* state */ + off_t logical_offset; /* fake offset */ + off_t physical_offset; /* actual offset */ + size_t block_size; /* current size */ + size_t skip_size; /* size from block start to reach data */ + size_t data_size; /* usable size in a block */ + + size_t logical_size; +} sfh_io_data; + + +static size_t sfh_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, sfh_io_data* data) { + size_t total_read = 0; + + + /* re-start when previous offset (can't map logical<>physical offsets) */ + if (data->logical_offset < 0 || offset < data->logical_offset) { + data->physical_offset = data->stream_offset; + data->logical_offset = 0x00; + data->data_size = 0; + } + + /* read blocks */ + while (length > 0) { + + /* ignore EOF */ + if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { + break; + } + + /* process new block */ + if (data->data_size == 0) { + data->skip_size = 0x10; /* skip 0x10 garbage on every block */ + data->data_size = data->block_size - 0x10; + } + + /* move to next block */ + if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size; + data->data_size = 0; + continue; + } + + /* read data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + +static size_t sfh_io_size(STREAMFILE *streamfile, sfh_io_data* data) { + uint8_t buf[1]; + + if (data->logical_size) + return data->logical_size; + + /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ + sfh_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles deinterleaving of SFH blocked streams */ +static STREAMFILE* setup_sfh_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t block_size, size_t clean_size, const char* extension) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + sfh_io_data io_data = {0}; + size_t io_data_size = sizeof(sfh_io_data); + + io_data.stream_offset = stream_offset; + io_data.stream_size = get_streamfile_size(streamFile) - stream_offset; + io_data.block_size = block_size; + io_data.logical_offset = -1; /* force phys offset reset */ + + /* setup subfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, sfh_io_read,sfh_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_buffer_streamfile(new_streamFile,0); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_clamp_streamfile(new_streamFile,0x00, clean_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + if (extension) { + new_streamFile = open_fakename_streamfile(temp_streamFile, NULL,extension); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + } + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +#endif /* _SFH_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd_sscf.c b/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd_sscf.c index 56b4377ee..2bcb41780 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd_sscf.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sqex_scd_sscf.c @@ -39,7 +39,7 @@ VGMSTREAM * init_vgmstream_scd_sscf(STREAMFILE *streamFile) { total_subsongs = 0; for (i = 0; i < entries; i++) { off_t entry_offset = 0x20 + (0x20*i); - off_t stream_offset; + off_t entry_stream_offset; /* skip dummies */ if (read_32bitLE(entry_offset+0x08,streamFile) == 0) /* size 0 */ @@ -49,16 +49,16 @@ VGMSTREAM * init_vgmstream_scd_sscf(STREAMFILE *streamFile) { /* skip repeated sounds */ is_dupe = 0; - stream_offset = read_32bitLE(entry_offset+0x04,streamFile); + entry_stream_offset = read_32bitLE(entry_offset+0x04,streamFile); for (j = 0; j < total_subsongs; j++) { - if (stream_offset == stream_offsets[j]) { + if (entry_stream_offset == stream_offsets[j]) { is_dupe = 1; break; } } if (is_dupe) continue; - stream_offsets[total_subsongs] = stream_offset; + stream_offsets[total_subsongs] = entry_stream_offset; /* ok */ total_subsongs++; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/str_wav.c b/Frameworks/vgmstream/vgmstream/src/meta/str_wav.c index ffb0c4032..aa72e9e2d 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/str_wav.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/str_wav.c @@ -2,7 +2,7 @@ #include "../coding/coding.h" -typedef enum { PSX, DSP, XBOX, WMA } strwav_codec; +typedef enum { PSX, DSP, XBOX, WMA, IMA } strwav_codec; typedef struct { int32_t channels; int32_t sample_rate; @@ -126,6 +126,12 @@ VGMSTREAM * init_vgmstream_str_wav(STREAMFILE *streamFile) { goto fail; /* only 2ch+..+2ch layout is known */ break; + case IMA: + vgmstream->coding_type = coding_BLITZ_IMA; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = strwav.interleave; + break; + #ifdef VGM_USE_FFMPEG case WMA: { ffmpeg_codec_data *ffmpeg_data = NULL; @@ -330,6 +336,27 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { return 1; } + /* Zapper: One Wicked Cricket! (PC)[2005] */ + if ( read_32bitBE(0x04,streamHeader) == 0x00000900 && + read_32bitLE(0x24,streamHeader) == read_32bitLE(0x114,streamHeader) && /* sample rate repeat */ + read_32bitLE(0x28,streamHeader) == 0x10 && + read_32bitLE(0x12c,streamHeader) == header_size /* ~0x130 */ + ) { + strwav->num_samples = read_32bitLE(0x20,streamHeader); + strwav->sample_rate = read_32bitLE(0x24,streamHeader); + strwav->flags = read_32bitLE(0x2c,streamHeader); + strwav->loop_start = read_32bitLE(0x54,streamHeader); + strwav->loop_end = read_32bitLE(0x30,streamHeader); + + strwav->channels = read_32bitLE(0xF8,streamHeader) * (strwav->flags & 0x02 ? 2 : 1); /* tracks of 2/1ch */ + strwav->loop_flag = strwav->flags & 0x01; + strwav->interleave = strwav->channels > 2 ? 0x8000 : 0x10000; + + strwav->codec = IMA; + //;VGM_LOG("STR+WAV: header Zapper (PC)\n"); + return 1; + } + /* Pac-Man World 3 (GC)[2005] */ /* SpongeBob SquarePants: Creature from the Krusty Krab (GC)[2006] */ if ( read_32bitBE(0x04,streamHeader) == 0x00000800 && @@ -405,6 +432,7 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { read_32bitBE(0x04,streamHeader) == 0x00000700) && /* rare? */ read_32bitLE(0x08,streamHeader) != 0x00000000 && read_32bitBE(0x0c,streamHeader) == header_size && /* variable per DSP header */ + read_32bitBE(0x7c,streamHeader) != 0 && /* has DSP header */ read_32bitBE(0x38,streamHeader) == read_32bitBE(read_32bitBE(0x7c,streamHeader)+0x38,streamHeader) /* sample rate vs 1st DSP header */ ) { strwav->loop_start = 0; //read_32bitLE(0x24,streamHeader); //not ok? @@ -419,10 +447,31 @@ static int parse_header(STREAMFILE* streamHeader, strwav_header* strwav) { strwav->coefs_table = 0x7c; strwav->codec = DSP; - //;VGM_LOG("STR+WAV: header Tak (Wii)\n"); + //;VGM_LOG("STR+WAV: header Tak/HOTD:O (Wii)\n"); return 1; } + /* The House of the Dead: Overkill (PS3)[2009] (not Blitz but still the same format) */ + if ((read_32bitBE(0x04,streamHeader) == 0x00000800 || + read_32bitBE(0x04,streamHeader) == 0x00000700) && /* rare? */ + read_32bitLE(0x08,streamHeader) != 0x00000000 && + read_32bitBE(0x0c,streamHeader) == header_size && /* variable per DSP header */ + read_32bitBE(0x7c,streamHeader) == 0 /* not DSP header */ + ) { + strwav->loop_start = 0; //read_32bitLE(0x24,streamHeader); //not ok? + strwav->num_samples = read_32bitBE(0x30,streamHeader); + strwav->loop_end = read_32bitBE(0x34,streamHeader); + strwav->sample_rate = read_32bitBE(0x38,streamHeader); + strwav->flags = read_32bitBE(0x3c,streamHeader); + + strwav->channels = read_32bitBE(0x70,streamHeader); /* tracks of 1ch */ + strwav->loop_flag = strwav->flags & 0x01; + strwav->interleave = strwav->channels > 4 ? 0x4000 : 0x8000; + + strwav->codec = PSX; + //;VGM_LOG("STR+WAV: header HOTD:O (PS3)\n"); + return 1; + } /* unknown */ goto fail; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/strm_abylight.c b/Frameworks/vgmstream/vgmstream/src/meta/strm_abylight.c new file mode 100644 index 000000000..c5f884da6 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/strm_abylight.c @@ -0,0 +1,71 @@ +#include "meta.h" +#include "../coding/coding.h" + + +/* .STRM - from Abylight 3DS games [Cursed Castilla (3DS)] */ +VGMSTREAM * init_vgmstream_strm_abylight(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, sample_rate; + size_t data_size; + + + /* check extension */ + if ( !check_extensions(streamFile,"strm") ) + goto fail; + + /* check header */ + if (read_32bitBE(0x00,streamFile) != 0x5354524D) /* "STRM" */ + goto fail; + if (read_32bitLE(0x04,streamFile) != 0x03E8) /* version 1000? */ + goto fail; + + loop_flag = 0; + channel_count = 2; /* there are various possible fields but all files are stereo */ + sample_rate = read_32bitLE(0x08,streamFile); + + start_offset = 0x1e; + data_size = read_32bitLE(0x10,streamFile); + if (data_size != get_streamfile_size(streamFile) - start_offset) + goto fail; + if (data_size != read_32bitLE(0x18,streamFile)) + goto fail; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count,loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = sample_rate; + vgmstream->num_samples = aac_get_samples(streamFile, start_offset, data_size); + + vgmstream->meta_type = meta_STRM_ABYLIGHT; + +#ifdef VGM_USE_FFMPEG + { + ffmpeg_codec_data *ffmpeg_data = NULL; + + ffmpeg_data = init_ffmpeg_offset(streamFile, start_offset,data_size); + if (!ffmpeg_data) goto fail; + vgmstream->codec_data = ffmpeg_data; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + /* apparently none, or maybe ~600 */ + //if (!ffmpeg_data->skipSamples) + // ffmpeg_set_skip_samples(ffmpeg_data, 1024); + //vgmstream->num_samples -= 1024; + } +#else + goto fail; +#endif + + /* open the file for reading */ + if ( !vgmstream_open_stream(vgmstream, streamFile, start_offset) ) + goto fail; + return vgmstream; + +fail: + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/sxd.c b/Frameworks/vgmstream/vgmstream/src/meta/sxd.c index 302b2fb84..3beec8c5a 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/sxd.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/sxd.c @@ -5,32 +5,62 @@ /* SXD - Sony/SCE's SNDX lib format (cousin of SGXD) [Gravity Rush, Freedom Wars, Soul Sacrifice PSV] */ VGMSTREAM * init_vgmstream_sxd(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; - STREAMFILE * streamHeader = NULL, *streamData = NULL; + STREAMFILE *streamHeader = NULL, *streamExternal = NULL, *streamData = NULL, *streamHead = NULL, *streamBody = NULL; off_t start_offset, chunk_offset, first_offset = 0x60, name_offset = 0; size_t chunk_size, stream_size = 0; - int is_dual = 0, is_external = 0; + int is_dual, is_external; int loop_flag, channels, codec, flags; int sample_rate, num_samples, loop_start_sample, loop_end_sample; uint32_t at9_config_data = 0; int total_subsongs, target_subsong = streamFile->stream_index; - /* check extension, case insensitive */ - /* .sxd: header+data (SXDF), .sxd1: header (SXDF) + .sxd2 = data (SXDS) */ - if (!check_extensions(streamFile,"sxd,sxd2")) goto fail; - is_dual = !check_extensions(streamFile,"sxd"); + /* checks */ + /* .sxd: header+data (SXDF) + * .sxd1: header (SXDF) + .sxd2 = data (SXDS) + * .sxd3: sxd1 + sxd2 pasted together (found in some PS4 games, ex. Fate Extella)*/ + if (!check_extensions(streamFile,"sxd,sxd2,sxd3")) + goto fail; - /* sxd1+sxd2: use sxd1 as header; otherwise use the current file as header */ - if (is_dual) { - if (read_32bitBE(0x00,streamFile) != 0x53584453) /* "SXDS" */ - goto fail; - streamHeader = open_streamfile_by_ext(streamFile, "sxd1"); - if (!streamHeader) goto fail; - } else { - streamHeader = streamFile; + /* setup head/body variations */ + if (check_extensions(streamFile,"sxd2")) { + /* sxd1+sxd2: open sxd1 as header */ + + streamHead = open_streamfile_by_ext(streamFile, "sxd1"); + if (!streamHead) goto fail; + + streamHeader = streamHead; + streamExternal = streamFile; + is_dual = 1; } - if (read_32bitBE(0x00,streamHeader) != 0x53584446) /* "SXDF" */ + else if (check_extensions(streamFile,"sxd3")) { + /* sxd3: make subfiles for head and body to simplify parsing */ + off_t sxd1_offset = 0x00; + size_t sxd1_size = read_32bitLE(0x08, streamFile); + off_t sxd2_offset = sxd1_size; + size_t sxd2_size = get_streamfile_size(streamFile) - sxd1_size; + + streamHead = setup_subfile_streamfile(streamFile, sxd1_offset, sxd1_size, "sxd1"); + if (!streamHead) goto fail; + + streamBody = setup_subfile_streamfile(streamFile, sxd2_offset, sxd2_size, "sxd2"); + if (!streamBody) goto fail; + + streamHeader = streamHead; + streamExternal = streamBody; + is_dual = 1; + } + else { + /* sxd: use the current file as header */ + streamHeader = streamFile; + streamExternal = NULL; + is_dual = 0; + } + + if (streamHeader && read_32bitBE(0x00,streamHeader) != 0x53584446) /* "SXDF" */ + goto fail; + if (streamExternal && read_32bitBE(0x00,streamExternal) != 0x53584453) /* "SXDS" */ goto fail; @@ -132,8 +162,9 @@ VGMSTREAM * init_vgmstream_sxd(STREAMFILE *streamFile) { goto fail; } + /* even dual files may have some non-external streams */ if (is_external) { - streamData = streamFile; + streamData = streamExternal; } else { streamData = streamHeader; } @@ -196,11 +227,13 @@ VGMSTREAM * init_vgmstream_sxd(STREAMFILE *streamFile) { if (!vgmstream_open_stream(vgmstream,streamData,start_offset)) goto fail; - if (is_dual) close_streamfile(streamHeader); + if (streamHead) close_streamfile(streamHead); + if (streamBody) close_streamfile(streamBody); return vgmstream; fail: - if (is_dual) close_streamfile(streamHeader); + if (streamHead) close_streamfile(streamHead); + if (streamBody) close_streamfile(streamBody); close_vgmstream(vgmstream); return NULL; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ta_aac.c b/Frameworks/vgmstream/vgmstream/src/meta/ta_aac.c index bbc78e46e..49de813e9 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ta_aac.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ta_aac.c @@ -1,7 +1,7 @@ #include "meta.h" #include "../coding/coding.h" -/* AAC - Tri-Ace Audio Container */ +/* AAC - tri-Ace (Aska engine) Audio Container */ /* Xbox 360 Variants (Star Ocean 4, End of Eternity, Infinite Undiscovery) */ VGMSTREAM * init_vgmstream_ta_aac_x360(STREAMFILE *streamFile) { @@ -297,12 +297,12 @@ VGMSTREAM * init_vgmstream_ta_aac_mobile(STREAMFILE *streamFile) { if (read_32bitLE(0x148, streamFile) != (0x40-0x04*channel_count)*2 / channel_count) goto fail; /* frame samples */ if (channel_count > 2) goto fail; /* unknown data layout */ - vgmstream->coding_type = coding_YAMAHA; + vgmstream->coding_type = coding_ASKA; vgmstream->layout_type = layout_none; - vgmstream->num_samples = yamaha_bytes_to_samples(data_size, channel_count); - vgmstream->loop_start_sample = yamaha_bytes_to_samples(read_32bitLE(0x130, streamFile), channel_count); - vgmstream->loop_end_sample = yamaha_bytes_to_samples(read_32bitLE(0x134, streamFile), channel_count); + vgmstream->num_samples = aska_bytes_to_samples(data_size, channel_count); + vgmstream->loop_start_sample = aska_bytes_to_samples(read_32bitLE(0x130, streamFile), channel_count); + vgmstream->loop_end_sample = aska_bytes_to_samples(read_32bitLE(0x134, streamFile), channel_count); break; default: diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txth.c b/Frameworks/vgmstream/vgmstream/src/meta/txth.c index 6bee9bb5d..24bb927b4 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txth.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/txth.c @@ -1,6 +1,7 @@ #include "meta.h" #include "../coding/coding.h" #include "../layout/layout.h" +#include "txth_streamfile.h" #define TXT_LINE_MAX 0x2000 @@ -16,7 +17,7 @@ typedef enum { DVI_IMA = 7, /* DVI IMA ADPCM (high nibble first) */ MPEG = 8, /* MPEG (MP3) */ IMA = 9, /* IMA ADPCM (low nibble first) */ - AICA = 10, /* AICA ADPCM (Dreamcast games) */ + YAMAHA = 10, /* YAMAHA (AICA) ADPCM (Dreamcast games) */ MSADPCM = 11, /* MS ADPCM (Windows games) */ NGC_DSP = 12, /* NGC DSP (Nintendo games) */ PCM8_U_int = 13, /* 8-bit unsigned PCM (interleaved) */ @@ -34,6 +35,7 @@ typedef enum { PCM4 = 25, /* 4-bit signed PCM (3rd and 4th gen games) */ PCM4_U = 26, /* 4-bit unsigned PCM (3rd and 4th gen games) */ OKI16 = 27, /* OKI ADPCM with 16-bit output (unlike OKI/VOX/Dialogic ADPCM's 12-bit) */ + AAC = 28, /* Advanced Audio Coding (raw without .mp4) */ } txth_type; typedef struct { @@ -56,6 +58,8 @@ typedef struct { uint32_t data_size; int data_size_set; uint32_t start_offset; + uint32_t next_offset; + uint32_t padding_size; int sample_type; uint32_t num_samples; @@ -73,6 +77,8 @@ typedef struct { uint32_t coef_spacing; uint32_t coef_big_endian; uint32_t coef_mode; + int coef_table_set; + uint8_t coef_table[0x02*16 * 16]; /* reasonable max */ int num_samples_data_size; @@ -84,6 +90,21 @@ typedef struct { uint32_t name_offset; uint32_t name_size; + int subfile_set; + uint32_t subfile_offset; + uint32_t subfile_size; + char subfile_extension[32]; + + uint32_t chunk_number; + uint32_t chunk_start; + uint32_t chunk_size; + uint32_t chunk_count; + uint32_t chunk_header_size; + uint32_t chunk_data_size; + int chunk_start_set; + int chunk_size_set; + int chunk_count_set; + /* original STREAMFILE and its type (may be an unsupported "base" file or a .txth) */ STREAMFILE *streamFile; int streamfile_is_txth; @@ -98,12 +119,10 @@ typedef struct { } txth_header; - +static VGMSTREAM *init_subfile(txth_header * txth); static STREAMFILE * open_txth(STREAMFILE * streamFile); +static void clean_txth(txth_header * txth); static int parse_txth(txth_header * txth); -static int parse_keyval(STREAMFILE * streamFile, txth_header * txth, const char * key, char * val); -static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value); -static int get_bytes_to_samples(txth_header * txth, uint32_t bytes); /* TXTH - an artificial "generic" header for headerless streams. @@ -128,8 +147,8 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { txth.streambody_opened = 0; } else { - /* accept base file (no need for ID or ext checks --if a companion .TXTH exists all is good) - * (player still needs to accept the streamfile's ext, so at worst rename to .vgmstream) */ + /* accept base file (no need for ID or ext checks --if a companion .TXTH exists all is good). + * player still needs to accept the streamfile's ext, so at worst rename to .vgmstream */ STREAMFILE * streamText = open_txth(streamFile); if (!streamText) goto fail; @@ -148,6 +167,13 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { if (!parse_txth(&txth)) goto fail; + /* special case of parsing subfiles */ + if (txth.subfile_set) { + VGMSTREAM *subfile_vgmstream = init_subfile(&txth); + clean_txth(&txth); + return subfile_vgmstream; + } + /* type to coding conversion */ switch (txth.codec) { @@ -163,7 +189,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { case MPEG: coding = coding_MPEG_layer3; break; /* we later find out exactly which */ #endif case IMA: coding = coding_IMA; break; - case AICA: coding = coding_AICA; break; + case YAMAHA: coding = coding_YAMAHA; break; case MSADPCM: coding = coding_MSADPCM; break; case NGC_DSP: coding = coding_NGC_DSP; break; case PCM8_U_int: coding = coding_PCM8_U_int; break; @@ -177,6 +203,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { case XMA1: case XMA2: case AC3: + case AAC: case FFMPEG: coding = coding_FFmpeg; break; #endif case PCFX: coding = coding_PCFX; break; @@ -226,7 +253,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { case coding_PSX_badflags: case coding_DVI_IMA: case coding_IMA: - case coding_AICA: + case coding_YAMAHA: case coding_APPLE_IMA4: vgmstream->interleave_block_size = txth.interleave; vgmstream->interleave_last_block_size = txth.interleave_last; @@ -245,8 +272,8 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { coding = coding_DVI_IMA_int; if (coding == coding_IMA) coding = coding_IMA_int; - if (coding == coding_AICA) - coding = coding_AICA_int; + if (coding == coding_YAMAHA) + coding = coding_YAMAHA_int; } /* to avoid endless loops */ @@ -256,7 +283,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { coding == coding_IMA_int || coding == coding_DVI_IMA_int || coding == coding_SDX2_int || - coding == coding_AICA_int) ) { + coding == coding_YAMAHA_int) ) { goto fail; } } else { @@ -264,10 +291,10 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { } /* setup adpcm */ - if (coding == coding_AICA || coding == coding_AICA_int) { - int i; - for (i=0;ichannels;i++) { - vgmstream->ch[i].adpcm_step_index = 0x7f; + if (coding == coding_YAMAHA || coding == coding_YAMAHA_int) { + int ch; + for (ch = 0; ch < vgmstream->channels; ch++) { + vgmstream->ch[ch].adpcm_step_index = 0x7f; } } @@ -317,10 +344,12 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { goto fail; /* only 2ch+..+2ch layout is known */ } break; + case coding_NGC_DTK: if (vgmstream->channels != 2) goto fail; vgmstream->layout_type = layout_none; break; + case coding_NGC_DSP: if (txth.channels > 1 && txth.codec_mode == 0) { if (!txth.interleave) goto fail; @@ -339,13 +368,19 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { } /* get coefs */ - for (i=0;ichannels;i++) { + for (i = 0; i < vgmstream->channels; i++) { int16_t (*read_16bit)(off_t , STREAMFILE*) = txth.coef_big_endian ? read_16bitBE : read_16bitLE; + int16_t (*get_16bit)(uint8_t * p) = txth.coef_big_endian ? get_16bitBE : get_16bitLE; /* normal/split coefs */ if (txth.coef_mode == 0) { /* normal mode */ for (j = 0; j < 16; j++) { - vgmstream->ch[i].adpcm_coef[j] = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2, txth.streamHead); + int16_t coef; + if (txth.coef_table_set) + coef = get_16bit(txth.coef_table + i*txth.coef_spacing + j*2); + else + coef = read_16bit(txth.coef_offset + i*txth.coef_spacing + j*2, txth.streamHead); + vgmstream->ch[i].adpcm_coef[j] = coef; } } else { /* split coefs */ @@ -360,6 +395,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { } break; + #ifdef VGM_USE_MPEG case coding_MPEG_layer3: vgmstream->layout_type = layout_none; @@ -372,7 +408,7 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { case coding_FFmpeg: { ffmpeg_codec_data *ffmpeg_data = NULL; - if (txth.codec == FFMPEG || txth.codec == AC3) { + if (txth.codec == FFMPEG || txth.codec == AC3 || txth.codec == AAC) { /* default FFmpeg */ ffmpeg_data = init_ffmpeg_offset(txth.streamBody, txth.start_offset,txth.data_size); if ( !ffmpeg_data ) goto fail; @@ -474,15 +510,66 @@ VGMSTREAM * init_vgmstream_txth(STREAMFILE *streamFile) { if ( !vgmstream_open_stream(vgmstream,txth.streamBody,txth.start_offset) ) goto fail; - if (txth.streamtext_opened) close_streamfile(txth.streamText); - if (txth.streamhead_opened) close_streamfile(txth.streamHead); - if (txth.streambody_opened) close_streamfile(txth.streamBody); + clean_txth(&txth); return vgmstream; fail: - if (txth.streamtext_opened) close_streamfile(txth.streamText); - if (txth.streamhead_opened) close_streamfile(txth.streamHead); - if (txth.streambody_opened) close_streamfile(txth.streamBody); + clean_txth(&txth); + close_vgmstream(vgmstream); + return NULL; +} + +static VGMSTREAM *init_subfile(txth_header * txth) { + VGMSTREAM *vgmstream = NULL; + char extension[PATH_LIMIT]; + STREAMFILE * streamSubfile = NULL; + + + if (txth->subfile_size == 0) + txth->subfile_size = txth->data_size - txth->subfile_offset; + if (txth->subfile_extension[0] == '\0') + get_streamfile_ext(txth->streamFile,txth->subfile_extension,sizeof(txth->subfile_extension)); + + /* must detect a potential infinite loop: + * - init_vgmstream enters TXTH and reads .txth + * - TXTH subfile calls init, nothing is detected + * - init_vgmstream enters TXTH and reads .txth + * - etc + * to avoid it we set a particular fake extension and detect it when reading .txth + */ + strcpy(extension, "subfile_txth."); + strcat(extension, txth->subfile_extension); + + streamSubfile = setup_subfile_streamfile(txth->streamBody, txth->subfile_offset, txth->subfile_size, extension); + if (!streamSubfile) goto fail; + + vgmstream = init_vgmstream_from_STREAMFILE(streamSubfile); + if (!vgmstream) goto fail; + + /* apply some fields */ + if (txth->sample_rate) + vgmstream->sample_rate = txth->sample_rate; + if (txth->num_samples) + vgmstream->num_samples = txth->num_samples; + + if (txth->loop_flag) { + vgmstream_force_loop(vgmstream, txth->loop_flag, txth->loop_start_sample, txth->loop_end_sample); + } + else if (txth->loop_flag_set && vgmstream->loop_flag) { + vgmstream_force_loop(vgmstream, 0, 0, 0); + } + + if (txth->chunk_count && txth->subsong_count) { + vgmstream->num_streams = txth->subsong_count; + } + //todo: other combos with subsongs + subfile? + + + close_streamfile(streamSubfile); + return vgmstream; + +fail: + close_streamfile(streamSubfile); close_vgmstream(vgmstream); return NULL; } @@ -497,6 +584,8 @@ static STREAMFILE * open_txth(STREAMFILE * streamFile) { /* try "(path/)(name.ext).txth" */ get_streamfile_name(streamFile,filename,PATH_LIMIT); + if (strstr(filename, "subfile_txth") != NULL) + return NULL; /* detect special case of subfile-within-subfile */ strcat(filename, ".txth"); streamText = open_streamfile(streamFile,filename); if (streamText) return streamText; @@ -536,6 +625,81 @@ static STREAMFILE * open_txth(STREAMFILE * streamFile) { return NULL; } +static void clean_txth(txth_header * txth) { + /* close stuff manually opened during parse */ + if (txth->streamtext_opened) close_streamfile(txth->streamText); + if (txth->streamhead_opened) close_streamfile(txth->streamHead); + if (txth->streambody_opened) close_streamfile(txth->streamBody); +} + +/* ****************************************************************** */ + +static void set_body_chunk(txth_header * txth) { + STREAMFILE *temp_streamFile = NULL; + + /* sets body "chunk" if all needed values are set + * (done inline for padding/get_samples/etc calculators to work) */ + //todo maybe should only be done once, or have some count to retrigger to simplify? + if (!txth->chunk_start_set || !txth->chunk_size_set || !txth->chunk_count_set) + return; + if (txth->chunk_size == 0 || txth->chunk_start > txth->data_size || txth->chunk_count == 0) + return; + if (!txth->streamBody) + return; + + /* treat chunks as subsongs */ + if (txth->subsong_count > 1) + txth->chunk_number = txth->target_subsong; + if (txth->chunk_number == 0) + txth->chunk_number = 1; + if (txth->chunk_number > txth->chunk_count) + return; + + { + txth_io_config_data cfg = {0}; + + cfg.chunk_start = txth->chunk_start; + cfg.chunk_header_size = txth->chunk_header_size; + cfg.chunk_data_size = txth->chunk_data_size; + cfg.chunk_size = txth->chunk_size; + cfg.chunk_count = txth->chunk_count; + cfg.chunk_number = txth->chunk_number - 1; /* 1-index to 0-index */ + + temp_streamFile = setup_txth_streamfile(txth->streamBody, cfg, txth->streambody_opened); + if (!temp_streamFile) return; + } + + + /* closing is handled by temp_streamFile */ + //if (txth->streambody_opened) { + // close_streamfile(txth->streamBody); + // txth->streamBody = NULL; + // txth->streambody_opened = 0; + //} + + txth->streamBody = temp_streamFile; + txth->streambody_opened = 1; + + /* cancel values once set, to avoid weirdness and possibly allow chunks-in-chunks? */ + txth->chunk_start_set = 0; + txth->chunk_size_set = 0; + txth->chunk_count_set = 0; + + /* re-apply */ + if (!txth->data_size_set) { + txth->data_size = get_streamfile_size(txth->streamBody); + } +} + +static int parse_keyval(STREAMFILE * streamFile, txth_header * txth, const char * key, char * val); +static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * val, uint32_t * out_value); +static int parse_string(STREAMFILE * streamFile, txth_header * txth, const char * val, char * str); +static int parse_coef_table(STREAMFILE * streamFile, txth_header * txth, const char * val, uint8_t * out_value, size_t out_size); +static int is_string(const char * val, const char * cmp); +static int is_substring(const char * val, const char * cmp); +static int get_bytes_to_samples(txth_header * txth, uint32_t bytes); +static int get_padding_size(txth_header * txth, int discard_empty); + /* Simple text parser of "key = value" lines. * The code is meh and error handling not exactly the best. */ static int parse_txth(txth_header * txth) { @@ -570,8 +734,8 @@ static int parse_txth(txth_header * txth) { txt_offset += bytes_read; - /* get key/val (ignores lead/trail spaces, stops at space/comment/separator) */ - ok = sscanf(line, " %[^ \t#=] = %[^ \t#\r\n] ", key,val); + /* get key/val (ignores lead spaces, stops at space/comment/separator) */ + ok = sscanf(line, " %[^ \t#=] = %[^\t#\r\n] ", key,val); if (ok != 2) /* ignore line if no key=val (comment or garbage) */ continue; @@ -585,7 +749,7 @@ static int parse_txth(txth_header * txth) { if (!txth->streamBody) goto fail; - if (txth->data_size > get_streamfile_size(txth->streamBody) - txth->start_offset || txth->data_size == 0) + if (txth->data_size > get_streamfile_size(txth->streamBody) - txth->start_offset || txth->data_size <= 0) txth->data_size = get_streamfile_size(txth->streamBody) - txth->start_offset; return 1; @@ -596,64 +760,88 @@ fail: static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char * key, char * val) { //;VGM_LOG("TXTH: key=%s, val=%s\n", key, val); - if (0==strcmp(key,"codec")) { - if (0==strcmp(val,"PSX")) txth->codec = PSX; - else if (0==strcmp(val,"XBOX")) txth->codec = XBOX; - else if (0==strcmp(val,"NGC_DTK")) txth->codec = NGC_DTK; - else if (0==strcmp(val,"DTK")) txth->codec = NGC_DTK; - else if (0==strcmp(val,"PCM16BE")) txth->codec = PCM16BE; - else if (0==strcmp(val,"PCM16LE")) txth->codec = PCM16LE; - else if (0==strcmp(val,"PCM8")) txth->codec = PCM8; - else if (0==strcmp(val,"SDX2")) txth->codec = SDX2; - else if (0==strcmp(val,"DVI_IMA")) txth->codec = DVI_IMA; - else if (0==strcmp(val,"MPEG")) txth->codec = MPEG; - else if (0==strcmp(val,"IMA")) txth->codec = IMA; - else if (0==strcmp(val,"AICA")) txth->codec = AICA; - else if (0==strcmp(val,"MSADPCM")) txth->codec = MSADPCM; - else if (0==strcmp(val,"NGC_DSP")) txth->codec = NGC_DSP; - else if (0==strcmp(val,"DSP")) txth->codec = NGC_DSP; - else if (0==strcmp(val,"PCM8_U_int")) txth->codec = PCM8_U_int; - else if (0==strcmp(val,"PSX_bf")) txth->codec = PSX_bf; - else if (0==strcmp(val,"MS_IMA")) txth->codec = MS_IMA; - else if (0==strcmp(val,"PCM8_U")) txth->codec = PCM8_U; - else if (0==strcmp(val,"APPLE_IMA4")) txth->codec = APPLE_IMA4; - else if (0==strcmp(val,"ATRAC3")) txth->codec = ATRAC3; - else if (0==strcmp(val,"ATRAC3PLUS")) txth->codec = ATRAC3PLUS; - else if (0==strcmp(val,"XMA1")) txth->codec = XMA1; - else if (0==strcmp(val,"XMA2")) txth->codec = XMA2; - else if (0==strcmp(val,"FFMPEG")) txth->codec = FFMPEG; - else if (0==strcmp(val,"AC3")) txth->codec = AC3; - else if (0==strcmp(val,"PCFX")) txth->codec = PCFX; - else if (0==strcmp(val,"PCM4")) txth->codec = PCM4; - else if (0==strcmp(val,"PCM4_U")) txth->codec = PCM4_U; - else if (0==strcmp(val,"OKI16")) txth->codec = OKI16; + /* CODEC */ + if (is_string(key,"codec")) { + if (is_string(val,"PSX")) txth->codec = PSX; + else if (is_string(val,"XBOX")) txth->codec = XBOX; + else if (is_string(val,"NGC_DTK")) txth->codec = NGC_DTK; + else if (is_string(val,"DTK")) txth->codec = NGC_DTK; + else if (is_string(val,"PCM16BE")) txth->codec = PCM16BE; + else if (is_string(val,"PCM16LE")) txth->codec = PCM16LE; + else if (is_string(val,"PCM8")) txth->codec = PCM8; + else if (is_string(val,"SDX2")) txth->codec = SDX2; + else if (is_string(val,"DVI_IMA")) txth->codec = DVI_IMA; + else if (is_string(val,"MPEG")) txth->codec = MPEG; + else if (is_string(val,"IMA")) txth->codec = IMA; + else if (is_string(val,"YAMAHA")) txth->codec = YAMAHA; + else if (is_string(val,"AICA")) txth->codec = YAMAHA; + else if (is_string(val,"MSADPCM")) txth->codec = MSADPCM; + else if (is_string(val,"NGC_DSP")) txth->codec = NGC_DSP; + else if (is_string(val,"DSP")) txth->codec = NGC_DSP; + else if (is_string(val,"PCM8_U_int")) txth->codec = PCM8_U_int; + else if (is_string(val,"PSX_bf")) txth->codec = PSX_bf; + else if (is_string(val,"MS_IMA")) txth->codec = MS_IMA; + else if (is_string(val,"PCM8_U")) txth->codec = PCM8_U; + else if (is_string(val,"APPLE_IMA4")) txth->codec = APPLE_IMA4; + else if (is_string(val,"ATRAC3")) txth->codec = ATRAC3; + else if (is_string(val,"ATRAC3PLUS")) txth->codec = ATRAC3PLUS; + else if (is_string(val,"XMA1")) txth->codec = XMA1; + else if (is_string(val,"XMA2")) txth->codec = XMA2; + else if (is_string(val,"FFMPEG")) txth->codec = FFMPEG; + else if (is_string(val,"AC3")) txth->codec = AC3; + else if (is_string(val,"PCFX")) txth->codec = PCFX; + else if (is_string(val,"PCM4")) txth->codec = PCM4; + else if (is_string(val,"PCM4_U")) txth->codec = PCM4_U; + else if (is_string(val,"OKI16")) txth->codec = OKI16; + else if (is_string(val,"AAC")) txth->codec = AAC; else goto fail; + + /* set common interleaves to simplify usage + * (do it here to in case it's overwritten later, possibly with 0 on purpose) */ + if (txth->interleave == 0) { + switch(txth->codec) { + case PSX: txth->interleave = 0x10; break; + case PSX_bf: txth->interleave = 0x10; break; + case NGC_DSP: txth->interleave = 0x08; break; + case PCM16LE: txth->interleave = 0x02; break; + case PCM16BE: txth->interleave = 0x02; break; + case PCM8: txth->interleave = 0x01; break; + case PCM8_U: txth->interleave = 0x01; break; + default: break; + } + } } - else if (0==strcmp(key,"codec_mode")) { + else if (is_string(key,"codec_mode")) { if (!parse_num(txth->streamHead,txth,val, &txth->codec_mode)) goto fail; } - else if (0==strcmp(key,"value_mul") || 0==strcmp(key,"value_*")) { + + /* VALUE MODIFIERS */ + else if (is_string(key,"value_mul") || is_string(key,"value_*")) { if (!parse_num(txth->streamHead,txth,val, &txth->value_mul)) goto fail; } - else if (0==strcmp(key,"value_div") || 0==strcmp(key,"value_/")) { + else if (is_string(key,"value_div") || is_string(key,"value_/")) { if (!parse_num(txth->streamHead,txth,val, &txth->value_div)) goto fail; } - else if (0==strcmp(key,"value_add") || 0==strcmp(key,"value_+")) { + else if (is_string(key,"value_add") || is_string(key,"value_+")) { if (!parse_num(txth->streamHead,txth,val, &txth->value_add)) goto fail; } - else if (0==strcmp(key,"value_sub") || 0==strcmp(key,"value_-")) { + else if (is_string(key,"value_sub") || is_string(key,"value_-")) { if (!parse_num(txth->streamHead,txth,val, &txth->value_sub)) goto fail; } - else if (0==strcmp(key,"id_value")) { + + /* ID VALUES */ + else if (is_string(key,"id_value")) { if (!parse_num(txth->streamHead,txth,val, &txth->id_value)) goto fail; } - else if (0==strcmp(key,"id_offset")) { + else if (is_string(key,"id_offset")) { if (!parse_num(txth->streamHead,txth,val, &txth->id_offset)) goto fail; if (txth->id_value != txth->id_offset) /* evaluate current ID */ goto fail; } - else if (0==strcmp(key,"interleave")) { - if (0==strcmp(val,"half_size")) { + + /* INTERLEAVE / FRAME SIZE */ + else if (is_string(key,"interleave")) { + if (is_string(val,"half_size")) { if (txth->channels == 0) goto fail; txth->interleave = txth->data_size / txth->channels; } @@ -661,8 +849,8 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char if (!parse_num(txth->streamHead,txth,val, &txth->interleave)) goto fail; } } - else if (0==strcmp(key,"interleave_last")) { - if (0==strcmp(val,"auto")) { + else if (is_string(key,"interleave_last")) { + if (is_string(val,"auto")) { if (txth->channels > 0 && txth->interleave > 0) txth->interleave_last = (txth->data_size % (txth->interleave * txth->channels)) / txth->channels; } @@ -670,31 +858,71 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char if (!parse_num(txth->streamHead,txth,val, &txth->interleave_last)) goto fail; } } - else if (0==strcmp(key,"channels")) { + + /* BASE CONFIG */ + else if (is_string(key,"channels")) { if (!parse_num(txth->streamHead,txth,val, &txth->channels)) goto fail; } - else if (0==strcmp(key,"sample_rate")) { + else if (is_string(key,"sample_rate")) { if (!parse_num(txth->streamHead,txth,val, &txth->sample_rate)) goto fail; } - else if (0==strcmp(key,"start_offset")) { + + /* DATA CONFIG */ + else if (is_string(key,"start_offset")) { if (!parse_num(txth->streamHead,txth,val, &txth->start_offset)) goto fail; + + + /* apply */ if (!txth->data_size_set) { - txth->data_size = !txth->streamBody ? 0 : - get_streamfile_size(txth->streamBody) - txth->start_offset; /* re-evaluate */ + + /* with subsongs we want to clamp data_size from this subsong start to next subsong start */ + txth->next_offset = txth->data_size; + if (txth->subsong_count > 1 && txth->target_subsong < txth->subsong_count) { + /* temp move to next start_offset and move back*/ + txth->target_subsong++; + parse_num(txth->streamHead,txth,val, &txth->next_offset); + txth->target_subsong--; + if (txth->next_offset < txth->start_offset) + txth->next_offset = 0; + } + + if (txth->data_size && txth->data_size > txth->next_offset && txth->next_offset) + txth->data_size = txth->next_offset; + if (txth->data_size && txth->data_size > txth->start_offset) + txth->data_size -= txth->start_offset; } } - else if (0==strcmp(key,"data_size")) { + else if (is_string(key,"padding_size")) { + if (is_string(val,"auto")) { + txth->padding_size = get_padding_size(txth, 0); + } + else if (is_string(val,"auto-empty")) { + txth->padding_size = get_padding_size(txth, 1); + } + else { + if (!parse_num(txth->streamHead,txth,val, &txth->padding_size)) goto fail; + } + + /* apply */ + if (!txth->data_size_set) { + if (txth->data_size && txth->data_size > txth->padding_size) + txth->data_size -= txth->padding_size; + } + } + else if (is_string(key,"data_size")) { if (!parse_num(txth->streamHead,txth,val, &txth->data_size)) goto fail; txth->data_size_set = 1; } - else if (0==strcmp(key,"sample_type")) { - if (0==strcmp(val,"samples")) txth->sample_type = 0; - else if (0==strcmp(val,"bytes")) txth->sample_type = 1; - else if (0==strcmp(val,"blocks")) txth->sample_type = 2; + + /* SAMPLES */ + else if (is_string(key,"sample_type")) { + if (is_string(val,"samples")) txth->sample_type = 0; + else if (is_string(val,"bytes")) txth->sample_type = 1; + else if (is_string(val,"blocks")) txth->sample_type = 2; else goto fail; } - else if (0==strcmp(key,"num_samples")) { - if (0==strcmp(val,"data_size")) { + else if (is_string(key,"num_samples")) { + if (is_string(val,"data_size")) { txth->num_samples = get_bytes_to_samples(txth, txth->data_size); txth->num_samples_data_size = 1; } @@ -706,7 +934,7 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char txth->num_samples = get_bytes_to_samples(txth, txth->num_samples * (txth->interleave*txth->channels)); } } - else if (0==strcmp(key,"loop_start_sample")) { + else if (is_string(key,"loop_start_sample")) { if (!parse_num(txth->streamHead,txth,val, &txth->loop_start_sample)) goto fail; if (txth->sample_type==1) txth->loop_start_sample = get_bytes_to_samples(txth, txth->loop_start_sample); @@ -715,8 +943,8 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char if (txth->loop_adjust) txth->loop_start_sample += txth->loop_adjust; } - else if (0==strcmp(key,"loop_end_sample")) { - if (0==strcmp(val,"data_size")) { + else if (is_string(key,"loop_end_sample")) { + if (is_string(val,"data_size")) { txth->loop_end_sample = get_bytes_to_samples(txth, txth->data_size); } else { @@ -729,7 +957,7 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char if (txth->loop_adjust) txth->loop_end_sample += txth->loop_adjust; } - else if (0==strcmp(key,"skip_samples")) { + else if (is_string(key,"skip_samples")) { if (!parse_num(txth->streamHead,txth,val, &txth->skip_samples)) goto fail; txth->skip_samples_set = 1; if (txth->sample_type==1) @@ -737,15 +965,15 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char if (txth->sample_type==2) txth->skip_samples = get_bytes_to_samples(txth, txth->skip_samples * (txth->interleave*txth->channels)); } - else if (0==strcmp(key,"loop_adjust")) { + else if (is_string(key,"loop_adjust")) { if (!parse_num(txth->streamHead,txth,val, &txth->loop_adjust)) goto fail; if (txth->sample_type==1) txth->loop_adjust = get_bytes_to_samples(txth, txth->loop_adjust); if (txth->sample_type==2) txth->loop_adjust = get_bytes_to_samples(txth, txth->loop_adjust * (txth->interleave*txth->channels)); } - else if (0==strcmp(key,"loop_flag")) { - if (0==strcmp(val,"auto")) { + else if (is_string(key,"loop_flag")) { + if (is_string(val,"auto")) { txth->loop_flag_auto = 1; } else { @@ -756,49 +984,70 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char } } } - else if (0==strcmp(key,"coef_offset")) { + + /* COEFS */ + else if (is_string(key,"coef_offset")) { if (!parse_num(txth->streamHead,txth,val, &txth->coef_offset)) goto fail; } - else if (0==strcmp(key,"coef_spacing")) { + else if (is_string(key,"coef_spacing")) { if (!parse_num(txth->streamHead,txth,val, &txth->coef_spacing)) goto fail; } - else if (0==strcmp(key,"coef_endianness")) { - if (val[0]=='B' && val[1]=='E') + else if (is_string(key,"coef_endianness")) { + if (is_string(val, "BE")) txth->coef_big_endian = 1; - else if (val[0]=='L' && val[1]=='E') + else if (is_string(val, "LE")) txth->coef_big_endian = 0; else if (!parse_num(txth->streamHead,txth,val, &txth->coef_big_endian)) goto fail; } - else if (0==strcmp(key,"coef_mode")) { + else if (is_string(key,"coef_mode")) { if (!parse_num(txth->streamHead,txth,val, &txth->coef_mode)) goto fail; } - else if (0==strcmp(key,"psx_loops")) { - if (!parse_num(txth->streamHead,txth,val, &txth->coef_mode)) goto fail; + else if (is_string(key,"coef_table")) { + if (!parse_coef_table(txth->streamHead,txth,val, txth->coef_table, sizeof(txth->coef_table))) goto fail; + txth->coef_table_set = 1; } - else if (0==strcmp(key,"subsong_count")) { + + /* SUBSONGS */ + else if (is_string(key,"subsong_count")) { if (!parse_num(txth->streamHead,txth,val, &txth->subsong_count)) goto fail; } - else if (0==strcmp(key,"subsong_offset")) { + else if (is_string(key,"subsong_offset")) { if (!parse_num(txth->streamHead,txth,val, &txth->subsong_offset)) goto fail; } - else if (0==strcmp(key,"name_offset")) { + else if (is_string(key,"name_offset")) { if (!parse_num(txth->streamHead,txth,val, &txth->name_offset)) goto fail; txth->name_offset_set = 1; /* special subsong adjustment */ if (txth->subsong_offset) txth->name_offset = txth->name_offset + txth->subsong_offset * (txth->target_subsong - 1); } - else if (0==strcmp(key,"name_size")) { + else if (is_string(key,"name_size")) { if (!parse_num(txth->streamHead,txth,val, &txth->name_size)) goto fail; } - else if (0==strcmp(key,"header_file")) { + + /* SUBFILES */ + else if (is_string(key,"subfile_offset")) { + if (!parse_num(txth->streamHead,txth,val, &txth->subfile_offset)) goto fail; + txth->subfile_set = 1; + } + else if (is_string(key,"subfile_size")) { + if (!parse_num(txth->streamHead,txth,val, &txth->subfile_size)) goto fail; + txth->subfile_set = 1; + } + else if (is_string(key,"subfile_extension")) { + if (!parse_string(txth->streamHead,txth,val, txth->subfile_extension)) goto fail; + txth->subfile_set = 1; + } + + /* HEADER/BODY CONFIG */ + else if (is_string(key,"header_file")) { if (txth->streamhead_opened) { close_streamfile(txth->streamHead); txth->streamHead = NULL; txth->streamhead_opened = 0; } - if (0==strcmp(val,"null")) { /* reset */ + if (is_string(val,"null")) { /* reset */ if (!txth->streamfile_is_txth) { txth->streamHead = txth->streamFile; } @@ -816,14 +1065,14 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char txth->streamhead_opened = 1; } } - else if (0==strcmp(key,"body_file")) { + else if (is_string(key,"body_file")) { if (txth->streambody_opened) { close_streamfile(txth->streamBody); txth->streamBody = NULL; txth->streambody_opened = 0; } - if (0==strcmp(val,"null")) { /* reset */ + if (is_string(val,"null")) { /* reset */ if (!txth->streamfile_is_txth) { txth->streamBody = txth->streamFile; } @@ -846,14 +1095,124 @@ static int parse_keyval(STREAMFILE * streamFile_, txth_header * txth, const char txth->streamHead = txth->streamBody; } - txth->data_size = !txth->streamBody ? 0 : - get_streamfile_size(txth->streamBody) - txth->start_offset; /* re-evaluate */ + /* re-apply */ + if (!txth->data_size_set) { + txth->data_size = get_streamfile_size(txth->streamBody); + + /* maybe should be manually set again? */ + if (txth->data_size && txth->data_size > txth->next_offset && txth->next_offset) + txth->data_size = txth->next_offset; + if (txth->data_size && txth->data_size > txth->start_offset) + txth->data_size -= txth->start_offset; + if (txth->data_size && txth->data_size > txth->padding_size) + txth->data_size -= txth->padding_size; + } } + + /* CHUNKS */ + else if (is_string(key,"chunk_number")) { + if (!parse_num(txth->streamHead,txth,val, &txth->chunk_number)) goto fail; + } + else if (is_string(key,"chunk_start")) { + if (!parse_num(txth->streamHead,txth,val, &txth->chunk_start)) goto fail; + txth->chunk_start_set = 1; + set_body_chunk(txth); + } + else if (is_string(key,"chunk_header_size")) { + if (!parse_num(txth->streamHead,txth,val, &txth->chunk_header_size)) goto fail; + //txth->chunk_header_size_set = 1; + //set_body_chunk(txth); /* optional and should go before chunk_size */ + } + else if (is_string(key,"chunk_data_size")) { + if (!parse_num(txth->streamHead,txth,val, &txth->chunk_data_size)) goto fail; + //txth->chunk_data_size_set = 1; + //set_body_chunk(txth); /* optional and should go before chunk_size */ + } + else if (is_string(key,"chunk_size")) { + if (!parse_num(txth->streamHead,txth,val, &txth->chunk_size)) goto fail; + txth->chunk_size_set = 1; + set_body_chunk(txth); + } + else if (is_string(key,"chunk_count")) { + if (!parse_num(txth->streamHead,txth,val, &txth->chunk_count)) goto fail; + txth->chunk_count_set = 1; + set_body_chunk(txth); + } + + /* DEFAULT */ else { VGM_LOG("TXTH: unknown key=%s, val=%s\n", key,val); goto fail; } + //;VGM_LOG("TXTH: data_size=%x, start=%x, next=%x, padding=%x\n", txth->data_size, txth->start_offset, txth->next_offset, txth->padding_size); + + return 1; +fail: + return 0; +} + +static int is_string(const char * val, const char * cmp) { + int len = is_substring(val, cmp); + if (!len) return 0; + + /* also test that after string there aren't other values + * (comments are already removed but trailing spaces are allowed) */ + while (val[len] != '\0') { + if (val[len] != ' ') + return 0; + len++; + } + + return len; +} + +static int is_substring(const char * val, const char * cmp) { + int len = strlen(cmp); + if (strncmp(val, cmp, len) != 0) + return 0; + + /* string in val must be a full word (end with null or space) to + * avoid mistaking stuff like "interleave" with "interleave_last" + * (could also check , except when used for math */ + if (val[len] != '\0' && val[len] != ' ') + return 0; + + return len; +} + +static int parse_string(STREAMFILE * streamFile, txth_header * txth, const char * val, char * str) { + int n = 0; + + /* read string without trailing spaces */ + if (sscanf(val, " %s%n[^ ]%n", str, &n, &n) != 1) + return 0; + return n; +} + +static int parse_coef_table(STREAMFILE * streamFile, txth_header * txth, const char * val, uint8_t * out_value, size_t out_size) { + uint32_t byte; + int done = 0; + + /* read 2 char pairs = 1 byte ('N' 'N' 'M' 'M' = 0xNN 0xMM )*/ + while (val[0] != '\0') { + if (val[0] == ' ') { + val++; + continue; + } + + if (val[0] == '0' && val[1] == 'x') /* allow "0x" before values */ + val += 2; + if (sscanf(val, " %2x", &byte) != 1) + goto fail; + if (done + 1 >= out_size) + goto fail; + + out_value[done] = (uint8_t)byte; + done++; + val += 2; + } + return 1; fail: return 0; @@ -867,91 +1226,148 @@ static int parse_num(STREAMFILE * streamFile, txth_header * txth, const char * v uint32_t value_sub = txth->value_sub; uint32_t subsong_offset = txth->subsong_offset; - if (val[0] == '@') { /* offset */ - uint32_t offset = 0; - char ed1 = 'L', ed2 = 'E'; - int size = 4; - int big_endian = 0; - int hex = (val[1]=='0' && val[2]=='x'); + char op = ' '; + int brackets = 0; + uint32_t result = 0; - /* can happen when loading .txth and not setting body/head */ - if (!streamFile) - goto fail; + //;VGM_LOG("TXTH: initial val '%s'\n", val); - /* read exactly N fields in the expected format */ - if (strchr(val,':') && strchr(val,'$')) { - if (sscanf(val, hex ? "@%x:%c%c$%i" : "@%u:%c%c$%i", &offset, &ed1,&ed2, &size) != 4) goto fail; - } else if (strchr(val,':')) { - if (sscanf(val, hex ? "@%x:%c%c" : "@%u:%c%c", &offset, &ed1,&ed2) != 3) goto fail; - } else if (strchr(val,'$')) { - if (sscanf(val, hex ? "@%x$%i" : "@%u$%i", &offset, &size) != 2) goto fail; - } else { - if (sscanf(val, hex ? "@%x" : "@%u", &offset) != 1) goto fail; + + /* read "val" format: @(offset) (op) (field) (op) (number) ... */ + while (val[0] != '\0') { + uint32_t value = 0; + char type = val[0]; + int value_read = 0; + int n = 0; + + if (type == ' ') { /* ignore */ + n = 1; + } + else if (type == '(') { /* bracket */ + brackets++; + n = 1; + } + else if (type == ')') { /* bracket */ + if (brackets == 0) goto fail; + brackets--; + n = 1; + } + else if (type == '+' || type == '-' || type == '/' || type == '*') { /* op */ + op = type; + n = 1; + } + else if (type == '@') { /* offset */ + uint32_t offset = 0; + char ed1 = 'L', ed2 = 'E'; + int size = 4; + int big_endian = 0; + int hex = (val[1]=='0' && val[2]=='x'); + + /* can happen when loading .txth and not setting body/head */ + if (!streamFile) + goto fail; + + /* read exactly N fields in the expected format */ + if (strchr(val,':') && strchr(val,'$')) { + if (sscanf(val, hex ? "@%x:%c%c$%i%n" : "@%u:%c%c$%i%n", &offset, &ed1,&ed2, &size, &n) != 4) goto fail; + } else if (strchr(val,':')) { + if (sscanf(val, hex ? "@%x:%c%c%n" : "@%u:%c%c%n", &offset, &ed1,&ed2, &n) != 3) goto fail; + } else if (strchr(val,'$')) { + if (sscanf(val, hex ? "@%x$%i%n" : "@%u$%i%n", &offset, &size, &n) != 2) goto fail; + } else { + if (sscanf(val, hex ? "@%x%n" : "@%u%n", &offset, &n) != 1) goto fail; + } + + if (/*offset < 0 ||*/ offset > get_streamfile_size(streamFile)) + goto fail; + + if (ed1 == 'B' && ed2 == 'E') + big_endian = 1; + else if (!(ed1 == 'L' && ed2 == 'E')) + goto fail; + + if (subsong_offset) + offset = offset + subsong_offset * (txth->target_subsong - 1); + + switch(size) { + case 1: value = read_8bit(offset,streamFile); break; + case 2: value = big_endian ? (uint16_t)read_16bitBE(offset,streamFile) : (uint16_t)read_16bitLE(offset,streamFile); break; + case 3: value = (big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile)) & 0x00FFFFFF; break; + case 4: value = big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile); break; + default: goto fail; + } + value_read = 1; + } + else if (type >= '0' && type <= '9') { /* unsigned constant */ + int hex = (val[0]=='0' && val[1]=='x'); + + if (sscanf(val, hex ? "%x%n" : "%u%n", &value, &n) != 1) + goto fail; + value_read = 1; + } + else { /* known field */ + if ((n = is_substring(val,"interleave"))) value = txth->interleave; + else if ((n = is_substring(val,"interleave_last"))) value = txth->interleave_last; + else if ((n = is_substring(val,"channels"))) value = txth->channels; + else if ((n = is_substring(val,"sample_rate"))) value = txth->sample_rate; + else if ((n = is_substring(val,"start_offset"))) value = txth->start_offset; + else if ((n = is_substring(val,"data_size"))) value = txth->data_size; + else if ((n = is_substring(val,"num_samples"))) value = txth->num_samples; + else if ((n = is_substring(val,"loop_start_sample"))) value = txth->loop_start_sample; + else if ((n = is_substring(val,"loop_end_sample"))) value = txth->loop_end_sample; + else if ((n = is_substring(val,"subsong_count"))) value = txth->subsong_count; + else if ((n = is_substring(val,"subsong_offset"))) value = txth->subsong_offset; + else goto fail; + value_read = 1; } - if (/*offset < 0 ||*/ offset > get_streamfile_size(streamFile)) - goto fail; + /* apply simple left-to-right math though, for now "(" ")" are counted and validated + * (could use good ol' shunting-yard algo but whatevs) */ + if (value_read) { + //;VGM_ASSERT(op != ' ', "MIX: %i %c %i\n", result, op, value); + switch(op) { + case '+': value = result + value; break; + case '-': value = result - value; break; + case '*': value = result * value; break; + case '/': if (value == 0) goto fail; value = result / value; break; + default: break; + } + op = ' '; /* consume */ - if (ed1 == 'B' && ed2 == 'E') - big_endian = 1; - else if (!(ed1 == 'L' && ed2 == 'E')) - goto fail; - - if (subsong_offset) - offset = offset + subsong_offset * (txth->target_subsong - 1); - - switch(size) { - case 1: *out_value = read_8bit(offset,streamFile); break; - case 2: *out_value = big_endian ? (uint16_t)read_16bitBE(offset,streamFile) : (uint16_t)read_16bitLE(offset,streamFile); break; - case 3: *out_value = (big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile)) & 0x00FFFFFF; break; - case 4: *out_value = big_endian ? (uint32_t)read_32bitBE(offset,streamFile) : (uint32_t)read_32bitLE(offset,streamFile); break; - default: goto fail; + result = value; } - } - else if (val[0] >= '0' && val[0] <= '9') { /* unsigned constant */ - int hex = (val[0]=='0' && val[1]=='x'); - if (sscanf(val, hex ? "%x" : "%u", out_value)!=1) - goto fail; - } - else { /* known field */ - if (0==strcmp(val,"interleave")) *out_value = txth->interleave; - if (0==strcmp(val,"interleave_last")) *out_value = txth->interleave_last; - else if (0==strcmp(val,"channels")) *out_value = txth->channels; - else if (0==strcmp(val,"sample_rate")) *out_value = txth->sample_rate; - else if (0==strcmp(val,"start_offset")) *out_value = txth->start_offset; - else if (0==strcmp(val,"data_size")) *out_value = txth->data_size; - else if (0==strcmp(val,"num_samples")) *out_value = txth->num_samples; - else if (0==strcmp(val,"loop_start_sample")) *out_value = txth->loop_start_sample; - else if (0==strcmp(val,"loop_end_sample")) *out_value = txth->loop_end_sample; - else if (0==strcmp(val,"subsong_count")) *out_value = txth->subsong_count; - else if (0==strcmp(val,"subsong_offset")) *out_value = txth->subsong_offset; - else goto fail; + /* move to next field (if any) */ + val += n; + + //;VGM_LOG("TXTH: val='%s', n=%i, brackets=%i, result=%i\n", val, n, brackets, result); } - /* operators, but only if current value wasn't set to 0 right before */ + /* unbalanced brackets */ + if (brackets > 0) + goto fail; + + /* global operators, but only if current value wasn't set to 0 right before */ if (value_mul && txth->value_mul) - *out_value = (*out_value) * value_mul; + result = result * value_mul; if (value_div && txth->value_div) - *out_value = (*out_value) / value_div; + result = result / value_div; if (value_add && txth->value_add) - *out_value = (*out_value) + value_add; + result = result + value_add; if (value_sub && txth->value_sub) - *out_value = (*out_value) - value_sub; + result = result - value_sub; - //;VGM_LOG("TXTH: val=%s, read %u (0x%x)\n", val, *out_value, *out_value); + *out_value = result; + + //;VGM_LOG("TXTH: final result %u (0x%x)\n", result, result); return 1; fail: return 0; } static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) { - if (!txth->channels) - return 0; /* div-by-zero is no fun */ - switch(txth->codec) { case MS_IMA: - if (!txth->interleave) return 0; return ms_ima_bytes_to_samples(bytes, txth->interleave, txth->channels); case XBOX: return xbox_ima_bytes_to_samples(bytes, txth->channels); @@ -971,29 +1387,30 @@ static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) { case PCM4_U: return pcm_bytes_to_samples(bytes, txth->channels, 4); case MSADPCM: - if (!txth->interleave) return 0; return msadpcm_bytes_to_samples(bytes, txth->interleave, txth->channels); case ATRAC3: - if (!txth->interleave) return 0; return atrac3_bytes_to_samples(bytes, txth->interleave); case ATRAC3PLUS: - if (!txth->interleave) return 0; return atrac3plus_bytes_to_samples(bytes, txth->interleave); + case AAC: + return aac_get_samples(txth->streamBody, txth->start_offset, bytes); +#ifdef VGM_USE_MPEG + case MPEG: + return mpeg_get_samples(txth->streamBody, txth->start_offset, bytes); +#endif + case AC3: + return ac3_bytes_to_samples(bytes, txth->interleave, txth->channels); /* XMA bytes-to-samples is done at the end as the value meanings are a bit different */ case XMA1: case XMA2: return bytes; /* preserve */ - case AC3: - if (!txth->interleave) return 0; - return bytes / txth->interleave * 256 * txth->channels; - case IMA: case DVI_IMA: return ima_bytes_to_samples(bytes, txth->channels); - case AICA: - return aica_bytes_to_samples(bytes, txth->channels); + case YAMAHA: + return yamaha_bytes_to_samples(bytes, txth->channels); case PCFX: case OKI16: return oki_bytes_to_samples(bytes, txth->channels); @@ -1007,9 +1424,20 @@ static int get_bytes_to_samples(txth_header * txth, uint32_t bytes) { if (!txth->interleave) return 0; return (bytes / txth->interleave) * (txth->interleave - 2) * 2; - case MPEG: /* a bit complex */ case FFMPEG: /* too complex, try after init */ default: return 0; } } + +static int get_padding_size(txth_header * txth, int discard_empty) { + if (txth->data_size == 0 || txth->channels == 0) + return 0; + + switch(txth->codec) { + case PSX: + return ps_find_padding(txth->streamBody, txth->start_offset, txth->data_size, txth->channels, txth->interleave, discard_empty); + default: + return 0; + } +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txth_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/txth_streamfile.h new file mode 100644 index 000000000..5419c815a --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/txth_streamfile.h @@ -0,0 +1,161 @@ +#ifndef _TXTH_STREAMFILE_H_ +#define _TXTH_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + off_t chunk_start; + size_t chunk_size; + size_t chunk_header_size; + size_t chunk_data_size; + int chunk_count; + int chunk_number; +} txth_io_config_data; + +typedef struct { + /* config */ + txth_io_config_data cfg; + size_t stream_size; + + /* state */ + off_t logical_offset; /* fake offset */ + off_t physical_offset; /* actual offset */ + size_t block_size; /* current size */ + size_t skip_size; /* size from block start to reach data */ + size_t data_size; /* usable size in a block */ + + size_t logical_size; +} txth_io_data; + + +static size_t txth_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, txth_io_data* data) { + size_t total_read = 0; + + + /* re-start when previous offset (can't map logical<>physical offsets) */ + if (data->logical_offset < 0 || offset < data->logical_offset) { + data->physical_offset = data->cfg.chunk_start; + data->logical_offset = 0x00; + data->data_size = 0; + data->skip_size = 0; + } + + /* read blocks */ + while (length > 0) { + + /* ignore EOF */ + if (offset < 0 || data->physical_offset >= data->cfg.chunk_start + data->stream_size) { + break; + } + + /* process new block */ + if (data->data_size == 0) { + /* base sizes */ + data->block_size = data->cfg.chunk_size * data->cfg.chunk_count; + data->skip_size = data->cfg.chunk_size * data->cfg.chunk_number; + data->data_size = data->cfg.chunk_size; + + /* chunk size modifiers */ + if (data->cfg.chunk_header_size) { + data->skip_size += data->cfg.chunk_header_size; + data->data_size -= data->cfg.chunk_header_size; + } + if (data->cfg.chunk_data_size) { + data->data_size = data->cfg.chunk_data_size; + } + + /* clamp for games where last block is smaller */ //todo not correct for all cases + if (data->physical_offset + data->block_size > data->cfg.chunk_start + data->stream_size) { + data->block_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset; + data->skip_size = (data->block_size / data->cfg.chunk_count) * data->cfg.chunk_number; + } + if (data->physical_offset + data->data_size > data->cfg.chunk_start + data->stream_size) { + data->data_size = (data->cfg.chunk_start + data->stream_size) - data->physical_offset; + } + + } + + /* move to next block */ + if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size; + data->data_size = 0; + continue; + } + + /* read data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + +static size_t txth_io_size(STREAMFILE *streamfile, txth_io_data* data) { + uint8_t buf[1]; + + if (data->logical_size) + return data->logical_size; + + /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ + txth_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles deinterleaving of generic chunked streams */ +static STREAMFILE* setup_txth_streamfile(STREAMFILE *streamFile, txth_io_config_data cfg, int is_opened_streamfile) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + txth_io_data io_data = {0}; + size_t io_data_size = sizeof(txth_io_data); + + io_data.cfg = cfg; /* memcpy */ + io_data.stream_size = (get_streamfile_size(streamFile) - cfg.chunk_start); + io_data.logical_offset = -1; /* force phys offset reset */ + //io_data.logical_size = io_data.stream_size / cfg.chunk_count; //todo would help with performance but not ok if data_size is set + + + new_streamFile = streamFile; + + /* setup subfile */ + if (!is_opened_streamfile) { + /* if streamFile was opened by txth code we MUST close it once done (as it's now "fused"),, + * otherwise it was external to txth and must be wrapped to avoid closing it */ + new_streamFile = open_wrap_streamfile(new_streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + } + + new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, txth_io_read,txth_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + //new_streamFile = open_buffer_streamfile(new_streamFile,0); + //if (!new_streamFile) goto fail; + //temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +#endif /* _TXTH_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/txtp.c b/Frameworks/vgmstream/vgmstream/src/meta/txtp.c index faa6c4969..fa857616f 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/txtp.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/txtp.c @@ -1,16 +1,72 @@ #include "meta.h" #include "../coding/coding.h" #include "../layout/layout.h" +#include "../mixing.h" -#define TXT_LINE_MAX 0x2000 +#define TXTP_LINE_MAX 1024 +#define TXTP_MIXING_MAX 128 +#define TXTP_GROUP_MODE_SEGMENTED 'S' +#define TXTP_GROUP_MODE_LAYERED 'L' +#define TXTP_GROUP_REPEAT 'R' + +/* mixing info */ +typedef enum { + MIX_SWAP, + MIX_ADD, + MIX_ADD_VOLUME, + MIX_VOLUME, + MIX_LIMIT, + MIX_DOWNMIX, + MIX_KILLMIX, + MIX_UPMIX, + MIX_FADE, + + MACRO_VOLUME, + MACRO_TRACK, + MACRO_LAYER, + MACRO_CROSSTRACK, + MACRO_CROSSLAYER, + +} txtp_mix_t; typedef struct { - char filename[TXT_LINE_MAX]; + txtp_mix_t command; + /* common */ + int ch_dst; + int ch_src; + double vol; + + /* fade envelope */ + double vol_start; + double vol_end; + char shape; + int32_t sample_pre; + int32_t sample_start; + int32_t sample_end; + int32_t sample_post; + double time_pre; + double time_start; + double time_end; + double time_post; + + /* macros */ + int max; + uint32_t mask; + char mode; +} txtp_mix_data; + + +typedef struct { + char filename[TXTP_LINE_MAX]; + + int range_start; + int range_end; int subsong; + uint32_t channel_mask; - int channel_mappings_on; - int channel_mappings[32]; + int mixing_count; + txtp_mix_data mixing[TXTP_MIXING_MAX]; double config_loop_count; double config_fade_time; @@ -18,33 +74,65 @@ typedef struct { int config_ignore_loop; int config_force_loop; int config_ignore_fade; + + int sample_rate; + int loop_install; + int loop_end_max; + double loop_start_second; + int32_t loop_start_sample; + double loop_end_second; + int32_t loop_end_sample; + } txtp_entry; + +typedef struct { + int position; + char type; + int count; + char repeat; + + txtp_entry group_config; + +} txtp_group; + typedef struct { txtp_entry *entry; size_t entry_count; size_t entry_max; + txtp_group *group; + size_t group_count; + size_t group_max; + + VGMSTREAM* *vgmstream; + size_t vgmstream_count; + uint32_t loop_start_segment; uint32_t loop_end_segment; + int is_loop_keep; txtp_entry default_entry; int default_entry_set; - size_t is_layered; + int is_segmented; + int is_layered; + int is_single; } txtp_header; static txtp_header* parse_txtp(STREAMFILE* streamFile); -static void clean_txtp(txtp_header* txtp); +static void clean_txtp(txtp_header* txtp, int fail); static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current); +void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command); + +static int make_group_segment(txtp_header* txtp, int from, int count); +static int make_group_layer(txtp_header* txtp, int from, int count); /* TXTP - an artificial playlist-like format to play files with segments/layers/config */ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { - VGMSTREAM * vgmstream = NULL; + VGMSTREAM *vgmstream = NULL; txtp_header* txtp = NULL; - segmented_layout_data *data_s = NULL; - layered_layout_data * data_l = NULL; int i; @@ -52,168 +140,370 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { if (!check_extensions(streamFile, "txtp")) goto fail; - /* read .txtp text file to get segments */ + /* read .txtp with all files and config */ txtp = parse_txtp(streamFile); if (!txtp) goto fail; + /* post-process */ + { + if (txtp->entry_count == 0) + goto fail; - if (txtp->entry_count == 0) - goto fail; + txtp->vgmstream = calloc(txtp->entry_count, sizeof(VGMSTREAM*)); + if (!txtp->vgmstream) goto fail; + + txtp->vgmstream_count = txtp->entry_count; + } - if (txtp->entry_count == 1 && !txtp->loop_start_segment) { - /* single file */ - STREAMFILE* temp_streamFile = open_streamfile_by_filename(streamFile, txtp->entry[0].filename); - if (!temp_streamFile) goto fail; - temp_streamFile->stream_index = txtp->entry[0].subsong; + /* detect single files before grouping */ + if (txtp->group_count == 0 && txtp->vgmstream_count == 1) { + txtp->is_single = 1; + txtp->is_segmented = 0; + txtp->is_layered = 0; + } - vgmstream = init_vgmstream_from_STREAMFILE(temp_streamFile); + + /* open all entry files first as they'll be modified by modes */ + for (i = 0; i < txtp->vgmstream_count; i++) { + STREAMFILE* temp_streamFile = open_streamfile_by_filename(streamFile, txtp->entry[i].filename); + if (!temp_streamFile) { + VGM_LOG("TXTP: cannot open streamfile for %s\n", txtp->entry[i].filename); + goto fail; + } + temp_streamFile->stream_index = txtp->entry[i].subsong; + + txtp->vgmstream[i] = init_vgmstream_from_STREAMFILE(temp_streamFile); close_streamfile(temp_streamFile); - if (!vgmstream) goto fail; - - apply_config(vgmstream, &txtp->entry[0]); - } - else if (txtp->is_layered) { - /* layered multi file */ - int channel_count = 0, loop_flag; - - /* init layout */ - data_l = init_layout_layered(txtp->entry_count); - if (!data_l) goto fail; - - /* open each segment subfile */ - for (i = 0; i < txtp->entry_count; i++) { - STREAMFILE* temp_streamFile = open_streamfile_by_filename(streamFile, txtp->entry[i].filename); - if (!temp_streamFile) goto fail; - temp_streamFile->stream_index = txtp->entry[i].subsong; - - data_l->layers[i] = init_vgmstream_from_STREAMFILE(temp_streamFile); - close_streamfile(temp_streamFile); - if (!data_l->layers[i]) goto fail; - - apply_config(data_l->layers[i], &txtp->entry[i]); - - /* get actual channel count after config */ - channel_count += data_l->layers[i]->channels; + if (!txtp->vgmstream[i]) { + VGM_LOG("TXTP: cannot open vgmstream for %s\n", txtp->entry[i].filename); + goto fail; } - /* setup layered VGMSTREAMs */ - if (!setup_layout_layered(data_l)) - goto fail; - - loop_flag = data_l->layers[0]->loop_flag; - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = data_l->layers[0]->sample_rate; - vgmstream->num_samples = data_l->layers[0]->num_samples; - vgmstream->loop_start_sample = data_l->layers[0]->loop_start_sample; - vgmstream->loop_end_sample = data_l->layers[0]->loop_end_sample; - - vgmstream->meta_type = meta_TXTP; - vgmstream->coding_type = data_l->layers[0]->coding_type; - vgmstream->layout_type = layout_layered; - - vgmstream->layout_data = data_l; + apply_config(txtp->vgmstream[i], &txtp->entry[i]); } - else { - /* segmented multi file */ - int num_samples, loop_start_sample = 0, loop_end_sample = 0; - int loop_flag, channel_count; - /* init layout */ - data_s = init_layout_segmented(txtp->entry_count); - if (!data_s) goto fail; - /* open each segment subfile */ - for (i = 0; i < txtp->entry_count; i++) { - STREAMFILE* temp_streamFile = open_streamfile_by_filename(streamFile, txtp->entry[i].filename); - if (!temp_streamFile) goto fail; - temp_streamFile->stream_index = txtp->entry[i].subsong; + /* group files as needed */ + for (i = 0; i < txtp->group_count; i++) { + txtp_group *grp = &txtp->group[i]; + int pos, groups; - data_s->segments[i] = init_vgmstream_from_STREAMFILE(temp_streamFile); - close_streamfile(temp_streamFile); - if (!data_s->segments[i]) goto fail; + //;VGM_LOG("TXTP: apply group %i%c%i%c\n",txtp->group[i].position,txtp->group[i].type,txtp->group[i].count,txtp->group[i].repeat); - apply_config(data_s->segments[i], &txtp->entry[i]); + /* special meaning of "all files" */ + if (grp->position < 0 || grp->position >= txtp->vgmstream_count) + grp->position = 0; + if (grp->count <= 0) + grp->count = txtp->vgmstream_count - grp->position; + + /* repeats N groups (trailing files are not grouped) */ + if (grp->repeat == TXTP_GROUP_REPEAT) { + groups = ((txtp->vgmstream_count - grp->position) / grp->count); + } + else { + groups = 1; } - /* setup segmented VGMSTREAMs */ - if (!setup_layout_segmented(data_s)) - goto fail; - - /* get looping and samples */ - if (txtp->loop_start_segment && !txtp->loop_end_segment) - txtp->loop_end_segment = txtp->entry_count; - loop_flag = (txtp->loop_start_segment > 0 && txtp->loop_start_segment <= txtp->entry_count); - num_samples = 0; - for (i = 0; i < data_s->segment_count; i++) { - - if (loop_flag && txtp->loop_start_segment == i+1) { - loop_start_sample = num_samples; - } - - num_samples += data_s->segments[i]->num_samples; - - if (loop_flag && txtp->loop_end_segment == i+1) { - loop_end_sample = num_samples; + /* as groups are compacted position goes 1 by 1 */ + for (pos = grp->position; pos < grp->position + groups; pos++) { + //;VGM_LOG("TXTP: group=%i, count=%i, groups=%i\n", pos, grp->count, groups); + switch(grp->type) { + case TXTP_GROUP_MODE_LAYERED: + if (!make_group_layer(txtp, pos, grp->count)) + goto fail; + break; + case TXTP_GROUP_MODE_SEGMENTED: + if (!make_group_segment(txtp, pos, grp->count)) + goto fail; + break; + default: + goto fail; } } - channel_count = data_s->segments[0]->channels; - - /* build the VGMSTREAM */ - vgmstream = allocate_vgmstream(channel_count,loop_flag); - if (!vgmstream) goto fail; - - vgmstream->sample_rate = data_s->segments[0]->sample_rate; - vgmstream->num_samples = num_samples; - vgmstream->loop_start_sample = loop_start_sample; - vgmstream->loop_end_sample = loop_end_sample; - - vgmstream->meta_type = meta_TXTP; - vgmstream->coding_type = data_s->segments[0]->coding_type; - vgmstream->layout_type = layout_segmented; - vgmstream->layout_data = data_s; + /* group may also have config (like downmixing) */ + apply_config(txtp->vgmstream[grp->position], &grp->group_config); } + /* final tweaks (should be integrated with the above?) */ + if (txtp->is_layered) { + if (!make_group_layer(txtp, 0, txtp->vgmstream_count)) + goto fail; + } + if (txtp->is_segmented) { + if (!make_group_segment(txtp, 0, txtp->vgmstream_count)) + goto fail; + } + if (txtp->is_single) { + /* special case of setting start_segment to force/overwrite looping + * (better to use #E but left for compatibility with older TXTPs) */ + if (txtp->loop_start_segment == 1 && !txtp->loop_end_segment) { + vgmstream_force_loop(txtp->vgmstream[0], 1, txtp->vgmstream[0]->loop_start_sample, txtp->vgmstream[0]->num_samples); + } + } + + + /* may happen if using mixed mode but some files weren't grouped */ + if (txtp->vgmstream_count != 1) { + VGM_LOG("TXTP: wrong final vgmstream count %i\n", txtp->vgmstream_count); + goto fail; + } /* apply default config to the resulting file */ if (txtp->default_entry_set) { - apply_config(vgmstream, &txtp->default_entry); + apply_config(txtp->vgmstream[0], &txtp->default_entry); } - clean_txtp(txtp); + vgmstream = txtp->vgmstream[0]; + + clean_txtp(txtp, 0); return vgmstream; fail: - clean_txtp(txtp); - close_vgmstream(vgmstream); - free_layout_segmented(data_s); - free_layout_layered(data_l); + clean_txtp(txtp, 1); return NULL; } -static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { - vgmstream->channel_mask = current->channel_mask; +static void update_vgmstream_list(VGMSTREAM* vgmstream, txtp_header* txtp, int position, int count) { + int i; - vgmstream->channel_mappings_on = current->channel_mappings_on; - if (vgmstream->channel_mappings_on) { - int ch; - for (ch = 0; ch < 32; ch++) { - vgmstream->channel_mappings[ch] = current->channel_mappings[ch]; + //;VGM_LOG("TXTP: compact position=%i count=%i, vgmstreams=%i\n", position, count, txtp->vgmstream_count); + + /* sets and compacts vgmstream list pulling back all following entries */ + txtp->vgmstream[position] = vgmstream; + for (i = position + count; i < txtp->vgmstream_count; i++) { + //;VGM_LOG("TXTP: copy %i to %i\n", i, i + 1 - count); + txtp->vgmstream[i + 1 - count] = txtp->vgmstream[i]; + } + + /* list can only become smaller, no need to alloc/free/etc */ + txtp->vgmstream_count = txtp->vgmstream_count + 1 - count; + //;VGM_LOG("TXTP: compact vgmstreams=%i\n", txtp->vgmstream_count); +} + +static int make_group_segment(txtp_header* txtp, int position, int count) { + VGMSTREAM * vgmstream = NULL; + segmented_layout_data *data_s = NULL; + int i, loop_flag = 0; + + + if (count == 1) { /* nothing to do */ + //;VGM_LOG("TXTP: ignored segments of 1\n"); + return 1; + } + + if (position + count > txtp->vgmstream_count || position < 0 || count < 0) { + VGM_LOG("TXTP: ignored segment position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count); + return 1; + } + + /* loop settings only make sense if this group becomes final vgmstream */ + if (position == 0 && txtp->vgmstream_count == count) { + if (txtp->loop_start_segment && !txtp->loop_end_segment) + txtp->loop_end_segment = count; + loop_flag = (txtp->loop_start_segment > 0 && txtp->loop_start_segment <= count); + } + + + /* init layout */ + data_s = init_layout_segmented(count); + if (!data_s) goto fail; + + /* copy each subfile */ + for (i = 0; i < count; i++) { + data_s->segments[i] = txtp->vgmstream[i + position]; + txtp->vgmstream[i + position] = NULL; /* will be freed by layout */ + } + + /* setup VGMSTREAMs */ + if (!setup_layout_segmented(data_s)) + goto fail; + + /* build the layout VGMSTREAM */ + vgmstream = allocate_segmented_vgmstream(data_s,loop_flag, txtp->loop_start_segment - 1, txtp->loop_end_segment - 1); + if (!vgmstream) goto fail; + + /* custom meta name if all parts don't match */ + for (i = 0; i < data_s->segment_count; i++) { + if (vgmstream->meta_type != data_s->segments[i]->meta_type) { + vgmstream->meta_type = meta_TXTP; + break; } } + /* fix loop keep */ + if (loop_flag && txtp->is_loop_keep) { + int32_t current_samples = 0; + for (i = 0; i < data_s->segment_count; i++) { + if (txtp->loop_start_segment == i+1 /*&& data_s->segments[i]->loop_start_sample*/) { + vgmstream->loop_start_sample = current_samples + data_s->segments[i]->loop_start_sample; + } + + current_samples += data_s->segments[i]->num_samples; + + if (txtp->loop_end_segment == i+1 && data_s->segments[i]->loop_end_sample) { + vgmstream->loop_end_sample = current_samples - data_s->segments[i]->num_samples + data_s->segments[i]->loop_end_sample; + } + } + } + + + /* set new vgmstream and reorder positions */ + update_vgmstream_list(vgmstream, txtp, position, count); + + return 1; +fail: + close_vgmstream(vgmstream); + if (!vgmstream) + free_layout_segmented(data_s); + return 0; +} + +static int make_group_layer(txtp_header* txtp, int position, int count) { + VGMSTREAM * vgmstream = NULL; + layered_layout_data * data_l = NULL; + int i; + + + if (count == 1) { /* nothing to do */ + //;VGM_LOG("TXTP: ignored layer of 1\n"); + return 1; + } + + if (position + count > txtp->vgmstream_count || position < 0 || count < 0) { + VGM_LOG("TXTP: ignored layer position=%i, count=%i, entries=%i\n", position, count, txtp->vgmstream_count); + return 1; + } + + + /* init layout */ + data_l = init_layout_layered(count); + if (!data_l) goto fail; + + /* copy each subfile */ + for (i = 0; i < count; i++) { + data_l->layers[i] = txtp->vgmstream[i + position]; + txtp->vgmstream[i + position] = NULL; /* will be freed by layout */ + } + + /* setup VGMSTREAMs */ + if (!setup_layout_layered(data_l)) + goto fail; + + /* build the layout VGMSTREAM */ + vgmstream = allocate_layered_vgmstream(data_l); + if (!vgmstream) goto fail; + + /* custom meta name if all parts don't match */ + for (i = 0; i < count; i++) { + if (vgmstream->meta_type != data_l->layers[i]->meta_type) { + vgmstream->meta_type = meta_TXTP; + break; + } + } + + + /* set new vgmstream and reorder positions */ + update_vgmstream_list(vgmstream, txtp, position, count); + + return 1; +fail: + close_vgmstream(vgmstream); + if (!vgmstream) + free_layout_layered(data_l); + return 0; +} + + +static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) { + vgmstream->config_loop_count = current->config_loop_count; vgmstream->config_fade_time = current->config_fade_time; vgmstream->config_fade_delay = current->config_fade_delay; vgmstream->config_ignore_loop = current->config_ignore_loop; vgmstream->config_force_loop = current->config_force_loop; vgmstream->config_ignore_fade = current->config_ignore_fade; + + if (current->sample_rate > 0) { + vgmstream->sample_rate = current->sample_rate; + } + + if (current->loop_install) { + if (current->loop_start_second > 0 || current->loop_end_second > 0) { + current->loop_start_sample = current->loop_start_second * vgmstream->sample_rate; + current->loop_end_sample = current->loop_end_second * vgmstream->sample_rate; + if (current->loop_end_sample > vgmstream->num_samples && + current->loop_end_sample - vgmstream->num_samples <= 0.1 * vgmstream->sample_rate) + current->loop_end_sample = vgmstream->num_samples; /* allow some rounding leeway */ + } + + if (current->loop_end_max) { + current->loop_end_sample = vgmstream->num_samples; + } + + vgmstream_force_loop(vgmstream, current->loop_install, current->loop_start_sample, current->loop_end_sample); + } + + /* add macro to mixing list */ + if (current->channel_mask) { + int ch; + for (ch = 0; ch < vgmstream->channels; ch++) { + if (!((current->channel_mask >> ch) & 1)) { + txtp_mix_data mix = {0}; + mix.ch_dst = ch + 1; + mix.vol = 0.0f; + add_mixing(current, &mix, MIX_VOLUME); + } + } + } + + /* copy mixing list (should be done last as some mixes depend on config) */ + if (current->mixing_count > 0) { + int m; + + for (m = 0; m < current->mixing_count; m++) { + txtp_mix_data mix = current->mixing[m]; + + switch(mix.command) { + /* base mixes */ + case MIX_SWAP: mixing_push_swap(vgmstream, mix.ch_dst, mix.ch_src); break; + case MIX_ADD: mixing_push_add(vgmstream, mix.ch_dst, mix.ch_src, 1.0); break; + case MIX_ADD_VOLUME: mixing_push_add(vgmstream, mix.ch_dst, mix.ch_src, mix.vol); break; + case MIX_VOLUME: mixing_push_volume(vgmstream, mix.ch_dst, mix.vol); break; + case MIX_LIMIT: mixing_push_limit(vgmstream, mix.ch_dst, mix.vol); break; + case MIX_UPMIX: mixing_push_upmix(vgmstream, mix.ch_dst); break; + case MIX_DOWNMIX: mixing_push_downmix(vgmstream, mix.ch_dst); break; + case MIX_KILLMIX: mixing_push_killmix(vgmstream, mix.ch_dst); break; + case MIX_FADE: + /* Convert from time to samples now that sample rate is final. + * Samples and time values may be mixed though, so it's done for every + * value (if one is 0 the other will be too, though) */ + if (mix.time_pre > 0.0) mix.sample_pre = mix.time_pre * vgmstream->sample_rate; + if (mix.time_start > 0.0) mix.sample_start = mix.time_start * vgmstream->sample_rate; + if (mix.time_end > 0.0) mix.sample_end = mix.time_end * vgmstream->sample_rate; + if (mix.time_post > 0.0) mix.sample_post = mix.time_post * vgmstream->sample_rate; + /* convert special meaning too */ + if (mix.time_pre < 0.0) mix.sample_pre = -1; + if (mix.time_post < 0.0) mix.sample_post = -1; + + mixing_push_fade(vgmstream, mix.ch_dst, mix.vol_start, mix.vol_end, mix.shape, + mix.sample_pre, mix.sample_start, mix.sample_end, mix.sample_post); + break; + + /* macro mixes */ + case MACRO_VOLUME: mixing_macro_volume(vgmstream, mix.vol, mix.mask); break; + case MACRO_TRACK: mixing_macro_track(vgmstream, mix.mask); break; + case MACRO_LAYER: mixing_macro_layer(vgmstream, mix.max, mix.mask, mix.mode); break; + case MACRO_CROSSTRACK: mixing_macro_crosstrack(vgmstream, mix.max); break; + case MACRO_CROSSLAYER: mixing_macro_crosslayer(vgmstream, mix.max, mix.mode); break; + + default: + break; + } + } + } } /* ********************************** */ @@ -238,25 +528,287 @@ static void clean_filename(char * filename) { } -static void get_double(const char * config, double *value) { - int n; - if (sscanf(config, "%lf%n", value,&n) != 1) { - *value = 0; - } + +/* sscanf 101: "matches = sscanf(string-from, string-commands, parameters...)" + * - reads linearly and matches "%" commands to input parameters as found + * - reads until string end (NULL) or not being able to match current parameter + * - returns number of matched % parameters until stop, or -1 if no matches and reached string end + * - must supply pointer param for every "%" in the string + * - %d/f: match number until end or *non-number* (so "%d" reads "5t" as "5") + * - %s: reads string (dangerous due to overflows and surprising as %s%d can't match numbers since string eats all chars) + * - %[^(chars)] match string with chars not in the list (stop reading at those chars) + * - %*(command) read but don't match (no need to supply parameterr) + * - " ": ignore all spaces until next non-space + * - other chars in string must exist: ("%dt t%dt" reads "5t t5t" as "5" and "5", while "t5t 5t" matches only first "5") + * - %n: special match (not counted in return value), chars consumed until that point (can appear and be set multiple times) + */ + +static int get_double(const char * config, double *value) { + int n, m; + double temp; + + m = sscanf(config, " %lf%n", &temp,&n); + if (m != 1 || temp < 0) + return 0; + + *value = temp; + return n; } +static int get_int(const char * config, int *value) { + int n,m; + int temp; + + m = sscanf(config, " %i%n", &temp,&n); + if (m != 1 || temp < 0) + return 0; + + *value = temp; + return n; +} + +static int get_time(const char * config, double *value_f, int32_t *value_i) { + int n,m; + int temp_i1, temp_i2; + double temp_f1, temp_f2; + char temp_c; + + /* test if format is hour: N:N(.n) or N_N(.n) */ + m = sscanf(config, " %i%c%i%n", &temp_i1,&temp_c,&temp_i2,&n); + if (m == 3 && (temp_c == ':' || temp_c == '_')) { + m = sscanf(config, " %lf%c%lf%n", &temp_f1,&temp_c,&temp_f2,&n); + if (m != 3 || temp_f1 < 0.0 || temp_f1 >= 60.0 || temp_f2 < 0.0 || temp_f2 >= 60.0) + return 0; + + *value_f = temp_f1 * 60.0 + temp_f2; + return n; + } + + /* test if format is seconds: N.n */ + m = sscanf(config, " %i.%i%n", &temp_i1,&temp_i2,&n); + if (m == 2) { + m = sscanf(config, " %lf%n", &temp_f1,&n); + if (m != 1 || temp_f1 < 0.0) + return 0; + *value_f = temp_f1; + return n; + } + + /* test is format is hex samples: 0xN */ + m = sscanf(config, " 0x%x%n", &temp_i1,&n); + if (m == 1) { + /* allow negative samples for special meanings */ + //if (temp_i1 < 0) + // return 0; + + *value_i = temp_i1; + return n; + } + + /* assume format is samples: N */ + m = sscanf(config, " %i%n", &temp_i1,&n); + if (m == 1) { + /* allow negative samples for special meanings */ + //if (temp_i1 < 0) + // return 0; + + *value_i = temp_i1; + return n; + } + + return 0; +} + +static int get_bool(const char * config, int *value) { + int n,m; + char temp; + + n = 0; /* init as it's not matched if c isn't */ + m = sscanf(config, " %c%n", &temp, &n); + if (m >= 1 && !(temp == '#' || temp == '\r' || temp == '\n')) + return 0; /* ignore if anything non-space/comment matched */ + + if (m >= 1 && temp == '#') + n--; /* don't consume separator when returning totals */ + *value = 1; + return n; +} + +static int get_mask(const char * config, uint32_t *value) { + int n, m, total_n = 0; + int temp1,temp2, r1, r2; + int i; + char cmd; + uint32_t mask = *value; + + while (config[0] != '\0') { + m = sscanf(config, " %c%n", &cmd,&n); /* consume comma */ + if (m == 1 && (cmd == ',' || cmd == '-')) { /* '-' is alt separator (space is ok too, implicitly) */ + config += n; + continue; + } + + m = sscanf(config, " %d%n ~ %d%n", &temp1,&n, &temp2,&n); + if (m == 1) { /* single values */ + r1 = temp1 - 1; + r2 = temp1 - 1; + } + else if (m == 2) { /* range */ + r1 = temp1 - 1; + r2 = temp2 - 1; + } + else { /* no more matches */ + break; + } + + if (n == 0 || r1 < 0 || r1 > 31 || r2 < 0 || r2 > 31) + break; + + for (i = r1; i < r2 + 1; i++) { + mask |= (1 << i); + } + + config += n; + total_n += n; + + if (config[0]== ',' || config[0]== '-') + config++; + } + + *value = mask; + return total_n; +} + + +static int get_fade(const char * config, txtp_mix_data *mix, int *out_n) { + int n, m, tn = 0; + char type, separator; + + m = sscanf(config, " %d %c%n", &mix->ch_dst, &type, &n); + if (n == 0 || m != 2) goto fail; + config += n; + tn += n; + + if (type == '^') { + /* full definition */ + m = sscanf(config, " %lf ~ %lf = %c @%n", &mix->vol_start, &mix->vol_end, &mix->shape, &n); + if (n == 0 || m != 3) goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_pre, &mix->sample_pre); + if (n == 0) goto fail; + config += n; + tn += n; + + m = sscanf(config, " %c%n", &separator, &n); + if (n == 0 || m != 1 || separator != '~') goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_start, &mix->sample_start); + if (n == 0) goto fail; + config += n; + tn += n; + + m = sscanf(config, " %c%n", &separator, &n); + if (n == 0 || m != 1 || separator != '+') goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_end, &mix->sample_end); + if (n == 0) goto fail; + config += n; + tn += n; + + m = sscanf(config, " %c%n", &separator, &n); + if (n == 0 || m != 1 || separator != '~') goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_post, &mix->sample_post); + if (n == 0) goto fail; + config += n; + tn += n; + } + else { + /* simplified definition */ + if (type == '{' || type == '(') { + mix->vol_start = 0.0; + mix->vol_end = 1.0; + } + else if (type == '}' || type == ')') { + mix->vol_start = 1.0; + mix->vol_end = 0.0; + } + else { + goto fail; + } + + mix->shape = type; /* internally converted */ + + mix->time_pre = -1.0; + mix->sample_pre = -1; + + n = get_time(config, &mix->time_start, &mix->sample_start); + if (n == 0) goto fail; + config += n; + tn += n; + + m = sscanf(config, " %c%n", &separator, &n); + if (n == 0 || m != 1 || separator != '+') goto fail; + config += n; + tn += n; + + n = get_time(config, &mix->time_end, &mix->sample_end); + if (n == 0) goto fail; + config += n; + tn += n; + + mix->time_post = -1.0; + mix->sample_post = -1; + } + + mix->time_end = mix->time_start + mix->time_end; /* defined as length */ + + *out_n = tn; + return 1; +fail: + return 0; +} + +void add_mixing(txtp_entry* cfg, txtp_mix_data* mix, txtp_mix_t command) { + if (cfg->mixing_count + 1 > TXTP_MIXING_MAX) { + VGM_LOG("TXTP: too many mixes\n"); + return; + } + + /* parsers reads ch1 = first, but for mixing code ch0 = first + * (if parser reads ch0 here it'll become -1 with special meaning in code) */ + mix->ch_dst--; + mix->ch_src--; + mix->command = command; + + cfg->mixing[cfg->mixing_count] = *mix; /* memcpy'ed */ + cfg->mixing_count++; +} + + static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filename) { - strcpy(current->filename, filename); + if (filename) + strcpy(current->filename, filename); current->subsong = cfg->subsong; current->channel_mask = cfg->channel_mask; - if (cfg->channel_mappings_on) { - int ch; - current->channel_mappings_on = cfg->channel_mappings_on; - for (ch = 0; ch < 32; ch++) { - current->channel_mappings[ch] = cfg->channel_mappings[ch]; + //*current = *cfg; /* don't memcopy to allow list additions */ //todo save list first then memcpy + + if (cfg->mixing_count > 0) { + int i; + for (i = 0; i < cfg->mixing_count; i++) { + current->mixing[current->mixing_count] = cfg->mixing[i]; + current->mixing_count++; } } @@ -266,146 +818,357 @@ static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filenam current->config_ignore_loop = cfg->config_ignore_loop; current->config_force_loop = cfg->config_force_loop; current->config_ignore_fade = cfg->config_ignore_fade; + + current->sample_rate = cfg->sample_rate; + current->loop_install = cfg->loop_install; + current->loop_end_max = cfg->loop_end_max; + current->loop_start_sample = cfg->loop_start_sample; + current->loop_start_second = cfg->loop_start_second; + current->loop_end_sample = cfg->loop_end_sample; + current->loop_end_second = cfg->loop_end_second; } -static int add_filename(txtp_header * txtp, char *filename, int is_default) { - int i, n; +static void parse_config(txtp_entry *cfg, char *config) { + /* parse config: #(commands) */ + int n, nc, nm, mc; + char command[TXTP_LINE_MAX] = {0}; + + cfg->range_start = 0; + cfg->range_end = 1; + + while (config != NULL) { + /* position in next #(command) */ + config = strchr(config, '#'); + if (!config) break; + //;VGM_LOG("TXTP: config='%s'\n", config); + + /* get command until next space/number/comment/end */ + command[0] = '\0'; + mc = sscanf(config, "#%n%[^ #0-9\r\n]%n", &nc, command, &nc); + //;VGM_LOG("TXTP: command='%s', nc=%i, mc=%i\n", command, nc, mc); + if (mc <= 0 && nc == 0) break; + + config[0] = '\0'; //todo don't modify input string and properly calculate filename end + + config += nc; /* skip '#' and command */ + + /* check command string (though at the moment we only use single letters) */ + if (strcmp(command,"c") == 0) { + /* channel mask: file.ext#c1,2 = play channels 1,2 and mutes rest */ + + config += get_mask(config, &cfg->channel_mask); + //;VGM_LOG("TXTP: channel_mask ");{int i; for (i=0;i<16;i++)VGM_LOG("%i ",(cfg->channel_mask>>i)&1);}VGM_LOG("\n"); + } + else if (strcmp(command,"m") == 0) { + /* channel mixing: file.ext#m(sub-command),(sub-command),etc */ + char cmd; + + while (config[0] != '\0') { + txtp_mix_data mix = {0}; + + //;VGM_LOG("TXTP: subcommand='%s'\n", config); + + //todo use strchr instead? + if (sscanf(config, " %c%n", &cmd, &n) == 1 && n != 0 && cmd == ',') { + config += n; + continue; + } + + if (sscanf(config, " %d - %d%n", &mix.ch_dst, &mix.ch_src, &n) == 2 && n != 0) { + //;VGM_LOG("TXTP: mix %i-%i\n", mix.ch_dst, mix.ch_src); + add_mixing(cfg, &mix, MIX_SWAP); /* N-M: swaps M with N */ + config += n; + continue; + } + + if ((sscanf(config, " %d + %d * %lf%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0) || + (sscanf(config, " %d + %d x %lf%n", &mix.ch_dst, &mix.ch_src, &mix.vol, &n) == 3 && n != 0)) { + //;VGM_LOG("TXTP: mix %i+%i*%f\n", mix.ch_dst, mix.ch_src, mix.vol); + add_mixing(cfg, &mix, MIX_ADD_VOLUME); /* N+M*V: mixes M*volume to N */ + config += n; + continue; + } + + if (sscanf(config, " %d + %d%n", &mix.ch_dst, &mix.ch_src, &n) == 2 && n != 0) { + //;VGM_LOG("TXTP: mix %i+%i\n", mix.ch_dst, mix.ch_src); + add_mixing(cfg, &mix, MIX_ADD); /* N+M: mixes M to N */ + config += n; + continue; + } + + if ((sscanf(config, " %d * %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0) || + (sscanf(config, " %d x %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { + //;VGM_LOG("TXTP: mix %i*%f\n", mix.ch_dst, mix.vol); + add_mixing(cfg, &mix, MIX_VOLUME); /* N*V: changes volume of N */ + config += n; + continue; + } + + if ((sscanf(config, " %d = %lf%n", &mix.ch_dst, &mix.vol, &n) == 2 && n != 0)) { + //;VGM_LOG("TXTP: mix %i=%f\n", mix.ch_dst, mix.vol); + add_mixing(cfg, &mix, MIX_LIMIT); /* N=V: limits volume of N */ + config += n; + continue; + } + + if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'D') { + //;VGM_LOG("TXTP: mix %iD\n", mix.ch_dst); + add_mixing(cfg, &mix, MIX_KILLMIX); /* ND: downmix N and all following channels */ + config += n; + continue; + } + + if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'd') { + //;VGM_LOG("TXTP: mix %id\n", mix.ch_dst); + add_mixing(cfg, &mix, MIX_DOWNMIX);/* Nd: downmix N only */ + config += n; + continue; + } + + if (sscanf(config, " %d%c%n", &mix.ch_dst, &cmd, &n) == 2 && n != 0 && cmd == 'u') { + //;VGM_LOG("TXTP: mix %iu\n", mix.ch_dst); + add_mixing(cfg, &mix, MIX_UPMIX); /* Nu: upmix N */ + config += n; + continue; + } + + if (get_fade(config, &mix, &n) != 0) { + //;VGM_LOG("TXTP: fade %d^%f~%f=%c@%f~%f+%f~%f\n", + // mix.ch_dst, mix.vol_start, mix.vol_end, mix.shape, + // mix.time_pre, mix.time_start, mix.time_end, mix.time_post); + add_mixing(cfg, &mix, MIX_FADE); /* N^V1~V2@T1~T2+T3~T4: fades volumes between positions */ + config += n; + continue; + } + + break; /* unknown mix/new command/end */ + } + } + else if (strcmp(command,"s") == 0 || (nc == 1 && config[0] >= '0' && config[0] <= '9')) { + /* subsongs: file.ext#s2 = play subsong 2, file.ext#2~10 = play subsong range */ + int subsong_start = 0, subsong_end = 0; + + //todo also advance config? + if (sscanf(config, " %d ~ %d", &subsong_start, &subsong_end) == 2) { + if (subsong_start > 0 && subsong_end > 0) { + cfg->range_start = subsong_start-1; + cfg->range_end = subsong_end; + } + //;VGM_LOG("TXTP: subsong range %i~%i\n", range_start, range_end); + } + else if (sscanf(config, " %d", &subsong_start) == 1) { + if (subsong_start > 0) { + cfg->range_start = subsong_start-1; + cfg->range_end = subsong_start; + } + //;VGM_LOG("TXTP: subsong single %i-%i\n", range_start, range_end); + } + else { /* wrong config, ignore */ + //;VGM_LOG("TXTP: subsong none\n"); + } + } + else if (strcmp(command,"i") == 0) { + config += get_bool(config, &cfg->config_ignore_loop); + //;VGM_LOG("TXTP: ignore_loop=%i\n", cfg->config_ignore_loop); + } + else if (strcmp(command,"E") == 0) { + config += get_bool(config, &cfg->config_force_loop); + //;VGM_LOG("TXTP: force_loop=%i\n", cfg->config_force_loop); + } + else if (strcmp(command,"F") == 0) { + config += get_bool(config, &cfg->config_ignore_fade); + //;VGM_LOG("TXTP: ignore_fade=%i\n", cfg->config_ignore_fade); + } + else if (strcmp(command,"l") == 0) { + config += get_double(config, &cfg->config_loop_count); + //;VGM_LOG("TXTP: loop_count=%f\n", cfg->config_loop_count); + } + else if (strcmp(command,"f") == 0) { + config += get_double(config, &cfg->config_fade_time); + //;VGM_LOG("TXTP: fade_time=%f\n", cfg->config_fade_time); + } + else if (strcmp(command,"d") == 0) { + config += get_double(config, &cfg->config_fade_delay); + //;VGM_LOG("TXTP: fade_delay %f\n", cfg->config_fade_delay); + } + else if (strcmp(command,"h") == 0) { + config += get_int(config, &cfg->sample_rate); + //;VGM_LOG("TXTP: sample_rate %i\n", cfg->sample_rate); + } + else if (strcmp(command,"I") == 0) { + n = get_time(config, &cfg->loop_start_second, &cfg->loop_start_sample); + if (n > 0) { /* first value must exist */ + config += n; + + n = get_time(config, &cfg->loop_end_second, &cfg->loop_end_sample); + if (n == 0) { /* second value is optional */ + cfg->loop_end_max = 1; + } + + config += n; + cfg->loop_install = 1; + } + + //;VGM_LOG("TXTP: loop_install %i (max=%i): %i %i / %f %f\n", cfg->loop_install, cfg->loop_end_max, + // cfg->loop_start_sample, cfg->loop_end_sample, cfg->loop_start_second, cfg->loop_end_second); + } + //todo cleanup + else if (strcmp(command,"@volume") == 0) { + txtp_mix_data mix = {0}; + + nm = get_double(config, &mix.vol); + config += nm; + + if (nm == 0) continue; + + nm = get_mask(config, &mix.mask); + config += nm; + + add_mixing(cfg, &mix, MACRO_VOLUME); + } + else if (strcmp(command,"@track") == 0 || + strcmp(command,"C") == 0 ) { + txtp_mix_data mix = {0}; + + nm = get_mask(config, &mix.mask); + config += nm; + if (nm == 0) continue; + + add_mixing(cfg, &mix, MACRO_TRACK); + } + else if (strcmp(command,"@layer-v") == 0 || + strcmp(command,"@layer-b") == 0 || + strcmp(command,"@layer-e") == 0) { + txtp_mix_data mix = {0}; + + nm = get_int(config, &mix.max); + config += nm; + if (nm == 0) continue; + + nm = get_mask(config, &mix.mask); + config += nm; + + mix.mode = command[7]; /* pass letter */ + add_mixing(cfg, &mix, MACRO_LAYER); + } + else if (strcmp(command,"@crosslayer-v") == 0 || + strcmp(command,"@crosslayer-b") == 0 || + strcmp(command,"@crosslayer-e") == 0 || + strcmp(command,"@crosstrack") == 0) { + txtp_mix_data mix = {0}; + txtp_mix_t type; + if (strcmp(command,"@crosstrack") == 0) { + type = MACRO_CROSSTRACK; + } + else { + type = MACRO_CROSSLAYER; + mix.mode = command[12]; /* pass letter */ + } + + nm = get_int(config, &mix.max); + config += nm; + if (nm == 0) continue; + + add_mixing(cfg, &mix, type); + } + else if (config[nc] == ' ') { + //;VGM_LOG("TXTP: comment\n"); + break; /* comment, ignore rest */ + } + else { + //;VGM_LOG("TXTP: unknown command\n"); + /* end, incorrect command, or possibly a comment or double ## comment too + * (shouldn't fail for forward compatibility) */ + break; + } + } +} + + + +static int add_group(txtp_header * txtp, char *line) { + int n, m; + txtp_group cfg = {0}; + + /* parse group: (position)(type)(count)(repeat) #(commands) */ + //;VGM_LOG("TXTP: parse group '%s'\n", line); + + m = sscanf(line, " %d%n", &cfg.position, &n); + if (m == 1) { + cfg.position--; /* externally 1=first but internally 0=first */ + line += n; + } + + m = sscanf(line, " %c%n", &cfg.type, &n); + if (m == 1) { + line += n; + } + + m = sscanf(line, " %d%n", &cfg.count, &n); + if (m == 1) { + line += n; + } + + m = sscanf(line, " %c%n", &cfg.repeat, &n); + if (m == 1 && cfg.repeat == TXTP_GROUP_REPEAT) { + line += n; + } + + + parse_config(&cfg.group_config, line); + + //;VGM_LOG("TXTP: parsed group %i%c%i%c\n",cfg.position+1,cfg.type,cfg.count,cfg.repeat); + + /* add final group */ + { + /* resize in steps if not enough */ + if (txtp->group_count+1 > txtp->group_max) { + txtp_group *temp_group; + + txtp->group_max += 5; + temp_group = realloc(txtp->group, sizeof(txtp_group) * txtp->group_max); + if (!temp_group) goto fail; + txtp->group = temp_group; + } + + /* new group */ + txtp->group[txtp->group_count] = cfg; /* memcpy */ + + txtp->group_count++; + } + + return 1; +fail: + return 0; +} + + +static int add_entry(txtp_header * txtp, char *filename, int is_default) { + int i; txtp_entry cfg = {0}; - size_t range_start, range_end; - const char separator = '#'; //;VGM_LOG("TXTP: filename=%s\n", filename); - /* parse config: file.ext#(command) */ + /* parse filename: file.ext#(commands) */ { char *config; - /* find config start (filenames and config can contain multiple dots and #, - * so this may be fooled by certain patterns of . and #) */ - config = strchr(filename, '.'); /* first dot (may be a false positive) */ - if (!config) /* extensionless */ - config = filename; - config = strchr(config,separator); /* next should be config (hopefully right after extension) */ - if (!config) /* no config */ - config = filename; + if (is_default) { + config = filename; /* multiple commands without filename */ + } + else { + /* find config start (filenames and config can contain multiple dots and #, + * so this may be fooled by certain patterns of . and #) */ + config = strchr(filename, '.'); /* first dot (may be a false positive) */ + if (!config) /* extensionless */ + config = filename; + config = strchr(config, '#'); /* next should be config */ + if (!config) /* no config */ + config = NULL; + } - range_start = 0; - range_end = 1; - do { - /* get config pointer but remove config from filename */ - config = strchr(config, separator); - if (!config) - continue; - //;VGM_LOG("TXTP: config=%s\n", config); - - config[0] = '\0'; - config++; - - - if (config[0] == 'c') { - /* channel mask: file.ext#c1,2 = play channels 1,2 and mutes rest */ - int ch; - - config++; - cfg.channel_mask = 0; - while (sscanf(config, "%d%n", &ch,&n) == 1) { - if (ch > 0 && ch <= 32) - cfg.channel_mask |= (1 << (ch-1)); - - config += n; - if (config[0]== ',' || config[0]== '-') /* "-" for PowerShell, may have problems with "," */ - config++; - else if (config[0] != '\0') - break; - }; - } - else if (config[0] == 'm') { - /* channel mappings: file.ext#m1-2,3-4 = swaps channels 1<>2 and 3<>4 */ - int ch_from = 0, ch_to = 0; - - config++; - cfg.channel_mappings_on = 1; - - while (config[0] != '\0') { - if (sscanf(config, "%d%n", &ch_from, &n) != 1) - break; - config += n; - if (config[0]== ',' || config[0]== '-') - config++; - else if (config[0] != '\0') - break; - - if (sscanf(config, "%d%n", &ch_to, &n) != 1) - break; - config += n; - if (config[0]== ',' || config[0]== '-') - config++; - else if (config[0] != '\0') - break; - - if (ch_from > 0 && ch_from <= 32 && ch_to > 0 && ch_to <= 32) { - cfg.channel_mappings[ch_from-1] = ch_to-1; - } - } - } - else if (config[0] == 's' || (config[0] >= '0' && config[0] <= '9')) { - /* subsongs: file.ext#s2 = play subsong 2, file.ext#2~10 = play subsong range */ - int subsong_start = 0, subsong_end = 0; - - if (config[0]== 's') - config++; - if (sscanf(config, "%d~%d", &subsong_start, &subsong_end) == 2) { - if (subsong_start > 0 && subsong_end > 0) { - range_start = subsong_start-1; - range_end = subsong_end; - } - } - else if (sscanf(config, "%u", &subsong_start) == 1) { - if (subsong_start > 0) { - range_start = subsong_start-1; - range_end = subsong_start; - } - } - else { - config = NULL; /* wrong config, ignore */ - } - } - else if (config[0] == 'i') { - config++; - cfg.config_ignore_loop = 1; - } - else if (config[0] == 'E') { - config++; - cfg.config_force_loop = 1; - } - else if (config[0] == 'F') { - config++; - cfg.config_ignore_fade = 1; - } - else if (config[0] == 'l') { - config++; - get_double(config, &cfg.config_loop_count); - } - else if (config[0] == 'f') { - config++; - get_double(config, &cfg.config_fade_time); - } - else if (config[0] == 'd') { - config++; - get_double(config, &cfg.config_fade_delay); - } - else if (config[0] == ' ') { - continue; /* likely a comment, find next # */ - } - else { - //;VGM_LOG("TXTP: unknown command '%c'\n", config[0]); - break; /* also possibly a comment too */ - } - - } while (config != NULL); - - //;VGM_LOG("TXTP: config: range %i~%i, mask=%x\n", range_start, range_end, channel_mask); + parse_config(&cfg, config); } @@ -415,12 +1178,12 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) { /* config that applies to all files */ if (is_default) { txtp->default_entry_set = 1; - add_config(&txtp->default_entry, &cfg, filename); + add_config(&txtp->default_entry, &cfg, NULL); return 1; } - /* add filenames */ - for (i = range_start; i < range_end; i++){ + /* add final entry */ + for (i = cfg.range_start; i < cfg.range_end; i++){ txtp_entry *current; /* resize in steps if not enough */ @@ -448,9 +1211,24 @@ fail: return 0; } +/* ************************************************************************ */ + +static int is_substring(const char * val, const char * cmp) { + int n; + char subval[TXTP_LINE_MAX] = {0}; + + /* read string without trailing spaces or comments/commands */ + if (sscanf(val, " %s%n[^ #\t\r\n]%n", subval, &n, &n) != 1) + return 0; + + if (0 != strcmp(subval,cmp)) + return 0; + return n; +} + static int parse_num(const char * val, uint32_t * out_value) { int hex = (val[0]=='0' && val[1]=='x'); - if (sscanf(val, hex ? "%x" : "%u", out_value)!=1) + if (sscanf(val, hex ? "%x" : "%u", out_value) != 1) goto fail; return 1; @@ -461,6 +1239,7 @@ fail: static int parse_keyval(txtp_header * txtp, const char * key, const char * val) { //;VGM_LOG("TXTP: key=val '%s'='%s'\n", key,val); + if (0==strcmp(key,"loop_start_segment")) { if (!parse_num(val, &txtp->loop_start_segment)) goto fail; } @@ -468,17 +1247,40 @@ static int parse_keyval(txtp_header * txtp, const char * key, const char * val) if (!parse_num(val, &txtp->loop_end_segment)) goto fail; } else if (0==strcmp(key,"mode")) { - if (0==strcmp(val,"layers")) { + if (is_substring(val,"layers")) { + txtp->is_segmented = 0; txtp->is_layered = 1; } + else if (is_substring(val,"segments")) { + txtp->is_segmented = 1; + txtp->is_layered = 0; + } + else if (is_substring(val,"mixed")) { + txtp->is_segmented = 0; + txtp->is_layered = 0; + } + else { + goto fail; + } + } + else if (0==strcmp(key,"loop_mode")) { + if (is_substring(val,"keep")) { + txtp->is_loop_keep = 1; + } else { goto fail; } } else if (0==strcmp(key,"commands")) { - char val2[TXT_LINE_MAX]; + char val2[TXTP_LINE_MAX]; strcpy(val2, val); /* copy since val is modified here but probably not important */ - if (!add_filename(txtp, val2, 1)) goto fail; + if (!add_entry(txtp, val2, 1)) goto fail; + } + else if (0==strcmp(key,"group")) { + char val2[TXTP_LINE_MAX]; + strcpy(val2, val); /* copy since val is modified here but probably not important */ + if (!add_group(txtp, val2)) goto fail; + } else { goto fail; @@ -499,6 +1301,9 @@ static txtp_header* parse_txtp(STREAMFILE* streamFile) { txtp = calloc(1,sizeof(txtp_header)); if (!txtp) goto fail; + /* defaults */ + txtp->is_segmented = 1; + /* empty file: use filename with config (ex. "song.ext#3.txtp") */ if (get_streamfile_size(streamFile) == 0) { @@ -511,7 +1316,7 @@ static txtp_header* parse_txtp(STREAMFILE* streamFile) { if (!ext) goto fail; /* ??? */ ext[0] = '\0'; - if (!add_filename(txtp, filename, 0)) + if (!add_entry(txtp, filename, 0)) goto fail; return txtp; @@ -522,21 +1327,21 @@ static txtp_header* parse_txtp(STREAMFILE* streamFile) { if ((uint16_t)read_16bitLE(0x00, streamFile) == 0xFFFE || (uint16_t)read_16bitLE(0x00, streamFile) == 0xFEFF) txt_offset = 0x02; - /* read lines */ + /* normal file: read and parse lines */ while (txt_offset < file_size) { - char line[TXT_LINE_MAX] = {0}; - char key[TXT_LINE_MAX] = {0}, val[TXT_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */ - char filename[TXT_LINE_MAX] = {0}; + char line[TXTP_LINE_MAX] = {0}; + char key[TXTP_LINE_MAX] = {0}, val[TXTP_LINE_MAX] = {0}; /* at least as big as a line to avoid overflows (I hope) */ + char filename[TXTP_LINE_MAX] = {0}; int ok, bytes_read, line_done; - bytes_read = get_streamfile_text_line(TXT_LINE_MAX,line, txt_offset,streamFile, &line_done); + bytes_read = get_streamfile_text_line(TXTP_LINE_MAX,line, txt_offset,streamFile, &line_done); if (!line_done) goto fail; txt_offset += bytes_read; - /* get key/val (ignores lead/trail spaces, stops at space/separator) */ - ok = sscanf(line, " %[^ \t#=] = %[^ \t\r\n] ", key,val); - if (ok == 2) { /* no key=val */ + /* get key/val (ignores lead/trail spaces, # may be commands or comments) */ + ok = sscanf(line, " %[^ \t#=] = %[^\t\r\n] ", key,val); + if (ok == 2) { /* key=val */ if (!parse_keyval(txtp, key, val)) /* read key/val */ goto fail; continue; @@ -550,21 +1355,32 @@ static txtp_header* parse_txtp(STREAMFILE* streamFile) { continue; /* simple comment */ /* filename with config */ - if (!add_filename(txtp, filename, 0)) + if (!add_entry(txtp, filename, 0)) goto fail; } return txtp; fail: - clean_txtp(txtp); + clean_txtp(txtp, 1); return NULL; } -static void clean_txtp(txtp_header* txtp) { +static void clean_txtp(txtp_header* txtp, int fail) { + int i, start; + if (!txtp) return; + /* returns first vgmstream on success so it's not closed */ + start = fail ? 0 : 1; + + for (i = start; i < txtp->vgmstream_count; i++) { + close_vgmstream(txtp->vgmstream[i]); + } + + free(txtp->vgmstream); + free(txtp->group); free(txtp->entry); free(txtp); } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c index a2a32ec99..20f4dffe9 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao.c @@ -8,7 +8,7 @@ #define BAO_MAX_CHAIN_COUNT 128 /* POP:TFS goes up to ~100 */ typedef enum { CODEC_NONE = 0, UBI_IMA, RAW_PCM, RAW_PSX, RAW_XMA1, RAW_XMA2_OLD, RAW_XMA2_NEW, RAW_AT3, RAW_AT3_105, FMT_AT3, RAW_DSP, FMT_OGG } ubi_bao_codec; -typedef enum { TYPE_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE } ubi_bao_type; +typedef enum { TYPE_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE, UBI_SILENCE } ubi_bao_type; typedef enum { FILE_NONE = 0, UBI_FORGE, UBI_FORGE_b } ubi_bao_file; typedef struct { @@ -16,6 +16,8 @@ typedef struct { size_t header_base_size; size_t header_skip; + int header_less_le_flag; /* horrid but not sure what to do */ + off_t header_id; off_t header_type; @@ -30,7 +32,7 @@ typedef struct { off_t audio_stream_type; off_t audio_prefetch_size; size_t audio_interleave; - int audio_channel_samples; + int audio_fix_psx_samples; int audio_external_and; int audio_loop_and; @@ -56,7 +58,7 @@ typedef struct { int layer_external_and; int layer_ignore_error; - //off_t silence_duration_float; + off_t silence_duration_float; ubi_bao_codec codec_map[16]; ubi_bao_file file_type; @@ -106,12 +108,13 @@ typedef struct { int stream_type; int layer_count; + int layer_channels[BAO_MAX_LAYER_COUNT]; int sequence_count; uint32_t sequence_chain[BAO_MAX_CHAIN_COUNT]; int sequence_loop; int sequence_single; - //float duration; + float duration; char resource_name[255]; @@ -201,12 +204,12 @@ VGMSTREAM * init_vgmstream_ubi_bao_spk(STREAMFILE *streamFile) { /* Variation of .pk: * - 0x00: 0x014B5053 ("SPK\01" LE) * - 0x04: BAO count - * - 0x08 * count: BAO ids inside + * - 0x08: BAO ids inside (0x04 * BAO count) * - per BAO count - * - 0x00: 1? - * - 0x04: id that references this? (ex. id of an event BAO) - * - 0x08: BAO size - * - 0x0c+: BAO data + * - 0x00: table count + * - 0x04: ids related to this BAO? (0x04 * table count) + * - 0x08/NN: BAO size + * - 0x0c/NN+: BAO data up to size * * BAOs reference .sbao by name (are considered atomic) so perhaps could * be considered a type of bigfile. @@ -396,7 +399,8 @@ static VGMSTREAM * init_vgmstream_ubi_bao_base(ubi_bao_header * bao, STREAMFILE vgmstream->layout_type = layout_none; vgmstream->num_samples = bao->num_samples; /* ffmpeg_data->totalSamples */ - VGM_ASSERT(bao->num_samples != ffmpeg_data->totalSamples, "UBI BAO: header samples differ\n"); + VGM_ASSERT(bao->num_samples != ffmpeg_data->totalSamples, + "UBI BAO: header samples %i vs ffmpeg %i differ\n", bao->num_samples, (uint32_t)ffmpeg_data->totalSamples); break; } @@ -444,7 +448,7 @@ static VGMSTREAM * init_vgmstream_ubi_bao_layer(ubi_bao_header *bao, STREAMFILE STREAMFILE* temp_streamFile = NULL; STREAMFILE * streamData = NULL; size_t full_stream_size = bao->stream_size; - int i; + int i, total_channels = 0; streamData = setup_bao_streamfile(bao, streamFile); if (!streamData) goto fail; @@ -461,6 +465,8 @@ static VGMSTREAM * init_vgmstream_ubi_bao_layer(ubi_bao_header *bao, STREAMFILE if (!temp_streamFile) goto fail; bao->stream_size = get_streamfile_size(temp_streamFile); + bao->channels = bao->layer_channels[i]; + total_channels += bao->layer_channels[i]; /* build the layer VGMSTREAM (standard sb with custom streamfile) */ data->layers[i] = init_vgmstream_ubi_bao_base(bao, streamFile, temp_streamFile); @@ -474,7 +480,7 @@ static VGMSTREAM * init_vgmstream_ubi_bao_layer(ubi_bao_header *bao, STREAMFILE goto fail; /* build the base VGMSTREAM */ - vgmstream = allocate_vgmstream(bao->channels * bao->layer_count, bao->loop_flag); + vgmstream = allocate_vgmstream(total_channels, bao->loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = meta_UBI_BAO; @@ -576,6 +582,7 @@ static VGMSTREAM * init_vgmstream_ubi_bao_sequence(ubi_bao_header *bao, STREAMFI if (!setup_layout_segmented(data)) goto fail; + /* build the base VGMSTREAM */ vgmstream = allocate_vgmstream(data->segments[0]->channels, !bao->sequence_single); if (!vgmstream) goto fail; @@ -605,9 +612,78 @@ fail: return NULL; } -//static VGMSTREAM * init_vgmstream_ubi_bao_silence(ubi_bao_header *bao, STREAMFILE *streamFile) { -// return NULL; -//} + +static size_t silence_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, void* data) { + int i; + for (i = 0; i < length; i++) { + dest[i] = 0; + } + return length; /* pretend we read zeroes */ +} +static size_t silence_io_size(STREAMFILE *streamfile, void* data) { + return 0x7FFFFFF; /* whatevs */ +} +static STREAMFILE* setup_silence_streamfile(STREAMFILE *streamFile) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + + /* setup custom streamfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(temp_streamFile, NULL,0, silence_io_read,silence_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +static VGMSTREAM * init_vgmstream_ubi_bao_silence(ubi_bao_header *bao, STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + STREAMFILE *temp_streamFile = NULL; + int channel_count, sample_rate; + + channel_count = bao->channels; + sample_rate = bao->sample_rate; + + /* by default silences don't have settings so let's pretend */ + if (channel_count == 0) + channel_count = 2; + if (sample_rate == 0) + sample_rate = 48000; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, 0); + if (!vgmstream) goto fail; + + vgmstream->meta_type = meta_UBI_BAO; + vgmstream->sample_rate = sample_rate; + + vgmstream->num_samples = bao->duration * sample_rate; + vgmstream->num_streams = bao->total_subsongs; + vgmstream->stream_size = vgmstream->num_samples * channel_count * 0x02; /* PCM size */ + + vgmstream->coding_type = coding_PCM16LE; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x02; + + temp_streamFile = setup_silence_streamfile(streamFile); + if ( !vgmstream_open_stream(vgmstream, temp_streamFile, 0x00) ) + goto fail; + + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_vgmstream(vgmstream); + close_streamfile(temp_streamFile); + return vgmstream; +} static VGMSTREAM * init_vgmstream_ubi_bao_header(ubi_bao_header * bao, STREAMFILE * streamFile) { @@ -618,12 +694,12 @@ static VGMSTREAM * init_vgmstream_ubi_bao_header(ubi_bao_header * bao, STREAMFIL goto fail; /* not uncommon */ } - ;VGM_LOG("UBI BAO: target at %x, h_id=%08x, s_id=%08x, p_id=%08x\n", - (uint32_t)bao->header_offset, bao->header_id, bao->stream_id, bao->prefetch_id); - ;VGM_LOG("UBI BAO: stream=%x, size=%x, res=%s\n", - (uint32_t)bao->stream_offset, bao->stream_size, (bao->is_external ? bao->resource_name : "internal")); - ;VGM_LOG("UBI BAO: type=%i, header=%x, extra=%x, prefetch=%x, size=%x\n", - bao->header_type, bao->header_size, bao->extra_size, (uint32_t)bao->prefetch_offset, bao->prefetch_size); + //;VGM_LOG("UBI BAO: target at %x, h_id=%08x, s_id=%08x, p_id=%08x\n", + // (uint32_t)bao->header_offset, bao->header_id, bao->stream_id, bao->prefetch_id); + //;VGM_LOG("UBI BAO: stream=%x, size=%x, res=%s\n", + // (uint32_t)bao->stream_offset, bao->stream_size, (bao->is_external ? bao->resource_name : "internal")); + //;VGM_LOG("UBI BAO: type=%i, header=%x, extra=%x, prefetch=%x, size=%x\n", + // bao->header_type, bao->header_size, bao->extra_size, (uint32_t)bao->prefetch_offset, bao->prefetch_size); switch(bao->type) { @@ -640,16 +716,15 @@ static VGMSTREAM * init_vgmstream_ubi_bao_header(ubi_bao_header * bao, STREAMFIL vgmstream = init_vgmstream_ubi_bao_sequence(bao, streamFile); break; - //case UBI_SILENCE: - // vgmstream = init_vgmstream_ubi_bao_silence(bao, streamFile); - // break; + case UBI_SILENCE: + vgmstream = init_vgmstream_ubi_bao_silence(bao, streamFile); + break; default: VGM_LOG("UBI BAO: subsong not found/parsed\n"); goto fail; } - if (!vgmstream) goto fail; strcpy(vgmstream->stream_name, bao->readable_name); @@ -729,8 +804,8 @@ static int parse_pk(ubi_bao_header * bao, STREAMFILE *streamFile) { bao_offset += bao_size; /* files simply concat BAOs */ } - ;VGM_LOG("UBI BAO: class "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(bao->classes[i],"%02x=%i ",i,bao->classes[i]); }} VGM_LOG("\n"); - ;VGM_LOG("UBI BAO: types "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(bao->types[i],"%02x=%i ",i,bao->types[i]); }} VGM_LOG("\n"); + //;VGM_LOG("UBI BAO: class "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(bao->classes[i],"%02x=%i ",i,bao->classes[i]); }} VGM_LOG("\n"); + //;VGM_LOG("UBI BAO: types "); {int i; for (i=0;i<16;i++){ VGM_ASSERT(bao->types[i],"%02x=%i ",i,bao->types[i]); }} VGM_LOG("\n"); close_streamfile(streamIndex); close_streamfile(streamTest); @@ -827,10 +902,6 @@ static int parse_type_audio(ubi_bao_header * bao, off_t offset, STREAMFILE* stre bao->stream_type = read_32bit(h_offset + bao->cfg.audio_stream_type, streamFile); - if (bao->loop_flag && bao->cfg.audio_channel_samples) { - bao->num_samples = bao->num_samples / bao->channels; - } - return 1; //fail: // return 0; @@ -919,7 +990,7 @@ static int parse_type_layer(ubi_bao_header * bao, off_t offset, STREAMFILE* stre /* get 1st layer header in extra table and validate all headers match */ table_offset = offset + bao->header_size + cues_size; - bao->channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile); + //bao->channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile); bao->sample_rate = read_32bit(table_offset + bao->cfg.layer_sample_rate, streamFile); bao->stream_type = read_32bit(table_offset + bao->cfg.layer_stream_type, streamFile); bao->num_samples = read_32bit(table_offset + bao->cfg.layer_num_samples, streamFile); @@ -933,15 +1004,14 @@ static int parse_type_layer(ubi_bao_header * bao, off_t offset, STREAMFILE* stre VGM_LOG("UBI BAO: layer headers don't match at %x\n", (uint32_t)table_offset); if (bao->cfg.layer_ignore_error) { - bao->layer_count -= 1; - break; + continue; } goto fail; } - /* unusual but happens, layers handle it fine [Rayman Raving Rabbids: TV Party (Wii) ex. 0x22000cbc.pk] */ - VGM_ASSERT_ONCE(bao->channels != channels, "UBI BAO: layer channels don't match at %x\n", (uint32_t)table_offset); + /* uncommonly channels may vary per layer [Rayman Raving Rabbids: TV Party (Wii) ex. 0x22000cbc.pk] */ + bao->layer_channels[i] = channels; /* can be +-1 */ if (bao->num_samples != num_samples && bao->num_samples + 1 == num_samples) { @@ -956,10 +1026,39 @@ fail: return 0; } +static int parse_type_silence(ubi_bao_header * bao, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = bao->big_endian ? read_32bitBE : read_32bitLE; + off_t h_offset = offset + bao->header_skip; + uint32_t duration_int; + float* duration_float; + + /* silence header */ + bao->type = UBI_SILENCE; + if (bao->cfg.silence_duration_float == 0) { + VGM_LOG("UBI BAO: silence duration not configured at %x\n", (uint32_t)offset); + goto fail; + } + + { + duration_int = (uint32_t)read_32bit(h_offset + bao->cfg.silence_duration_float, streamFile); + duration_float = (float*)&duration_int; + bao->duration = *duration_float; + } + + if (bao->duration <= 0) { + VGM_LOG("UBI BAO: bad duration %f at %x\n", bao->duration, (uint32_t)offset); + goto fail; + } + + return 1; +fail: + return 0; +} + /* adjust some common values */ static int parse_values(ubi_bao_header * bao, STREAMFILE *streamFile) { - if (bao->type == UBI_SEQUENCE) + if (bao->type == UBI_SEQUENCE || bao->type == UBI_SILENCE) return 1; /* common validations */ @@ -979,6 +1078,10 @@ static int parse_values(ubi_bao_header * bao, STREAMFILE *streamFile) { goto fail; } + if (bao->type == UBI_AUDIO && bao->codec == RAW_PSX && bao->cfg.audio_fix_psx_samples && bao->loop_flag) { //todo: loop flag only? + bao->num_samples = bao->num_samples / bao->channels; + } + /* set prefetch id */ if (bao->is_prefetched) { @@ -1015,7 +1118,7 @@ static int parse_offsets(ubi_bao_header * bao, STREAMFILE *streamFile) { off_t bao_offset; size_t bao_size; - if (bao->type == UBI_SEQUENCE) + if (bao->type == UBI_SEQUENCE || bao->type == UBI_SILENCE) return 1; if (!bao->is_external && bao->is_prefetched) { @@ -1036,7 +1139,8 @@ static int parse_offsets(ubi_bao_header * bao, STREAMFILE *streamFile) { if (bao->is_prefetched) { bao->prefetch_offset = bao->memory_skip; } - else if (bao->is_external) { + + if (bao->is_external) { bao->stream_offset = bao->stream_skip; } else { @@ -1153,9 +1257,14 @@ static int parse_header(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offs bao->header_size = bao->cfg.header_base_size; + /* hack for games with smaller than standard + * (can't use lowest size as other games also have extra unused field) */ + if (bao->cfg.header_less_le_flag && !bao->big_endian) { + bao->header_size -= 0x04; + } /* detect extra unused field in PC/Wii * (could be improved but no apparent flags or anything useful) */ - if (get_streamfile_size(streamFile) > offset + bao->header_size) { + else if (get_streamfile_size(streamFile) > offset + bao->header_size) { /* may read next BAO version, layer header, cues, resource table size, etc, always > 1 */ int32_t end_field = read_32bit(offset + bao->header_size, streamFile); @@ -1176,6 +1285,10 @@ static int parse_header(ubi_bao_header * bao, STREAMFILE *streamFile, off_t offs if (!parse_type_layer(bao, offset, streamFile)) goto fail; break; + case 0x08: + if (!parse_type_silence(bao, offset, streamFile)) + goto fail; + break; default: VGM_LOG("UBI BAO: unknown header type at %x\n", (uint32_t)offset); goto fail; @@ -1267,6 +1380,10 @@ static STREAMFILE * open_atomic_bao(ubi_bao_file file_type, uint32_t file_id, in streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; + snprintf(buf,buf_size, "Spanish_BAO_0x%08x", file_id); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + snprintf(buf,buf_size, "German_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; @@ -1275,7 +1392,11 @@ static STREAMFILE * open_atomic_bao(ubi_bao_file file_type, uint32_t file_id, in streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; - snprintf(buf,buf_size, "Spanish_BAO_0x%08x", file_id); + snprintf(buf,buf_size, "Japanese_BAO_0x%08x", file_id); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + + snprintf(buf,buf_size, "Korean_BAO_0x%08x", file_id); streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; @@ -1283,7 +1404,15 @@ static STREAMFILE * open_atomic_bao(ubi_bao_file file_type, uint32_t file_id, in streamBAO = open_streamfile_by_filename(streamFile, buf); if (streamBAO) return streamBAO; - /* there may be more per language */ + snprintf(buf,buf_size, "Czech_BAO_0x%08x", file_id); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + + snprintf(buf,buf_size, "Polish_BAO_0x%08x", file_id); + streamBAO = open_streamfile_by_filename(streamFile, buf); + if (streamBAO) return streamBAO; + + /* these are all of the languages that were referenced in Assassin's Creed exe (out of each platform), there may be more */ } else { snprintf(buf,buf_size, "BAO_0x%08x", file_id); @@ -1501,7 +1630,7 @@ static void config_bao_layer_m(ubi_bao_header * bao, off_t stream_id, off_t laye bao->cfg.layer_external_flag = external_flag; bao->cfg.layer_stream_size = stream_size; bao->cfg.layer_extra_size = extra_size; - bao->cfg.layer_prefetch_size = prefetch_size; /* possible flag: 0x3c */ + bao->cfg.layer_prefetch_size = prefetch_size; bao->cfg.layer_cue_count = cue_count; bao->cfg.layer_cue_labels = cue_labels; bao->cfg.layer_external_and = external_and; @@ -1515,6 +1644,12 @@ static void config_bao_layer_e(ubi_bao_header * bao, off_t entry_size, off_t sam bao->cfg.layer_num_samples = num_samples; } +static void config_bao_silence_f(ubi_bao_header * bao, off_t duration) { + /* silence headers in float value */ + bao->cfg.silence_duration_float = duration; +} + + static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { /* Ubi BAO evolved from Ubi SB and are conceptually quite similar, see that first. @@ -1525,14 +1660,15 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { * - 0x04+: mini header (varies with version, see parse_header) * * Then are divided into "classes": - * - 0x10000000: event (links by id to another event or header BAO) + * - 0x10000000: event (links by id to another event or header BAO, may set volume/reverb/filters/etc) * - 0x20000000: header * - 0x30000000: memory audio (in .pk/.bao) * - 0x40000000: project info * - 0x50000000: stream audio (in .spk/.sbao) * - 0x60000000: unused? * - 0x70000000: info? has a count+table of id-things - * - 0x80000000: unknown (some id/info?) + * - 0x80000000: unknown (some floats?) + * - 0x90000000: unknown (some kind of command config?), rare [Ghost Recon Future Soldier (PC)] * Class 1/2/3 are roughly equivalent to Ubi SB's section1/2/3, and class 4 is * basically .spN project files. * @@ -1581,18 +1717,20 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { switch(bao->version) { case 0x001B0100: /* Assassin's Creed (PS3/X360/PC)-atomic-forge */ - config_bao_entry(bao, 0xA4, 0x28); + config_bao_entry(bao, 0xA4, 0x28); /* PC: 0xA8, PS3/X360: 0xA4 */ config_bao_audio_b(bao, 0x08, 0x1c, 0x28, 0x34, 1, 1); /* 0x2c: prefetch flag? */ - config_bao_audio_m(bao, 0x44, 0x4c, 0x50, 0x58, 0x64, 0x74); + config_bao_audio_m(bao, 0x44, 0x48, 0x50, 0x58, 0x64, 0x74); bao->cfg.audio_interleave = 0x10; - bao->cfg.audio_channel_samples = 1; //todo check all looping ps-adpcm + bao->cfg.audio_fix_psx_samples = 1; config_bao_sequence(bao, 0x2c, 0x20, 0x1c, 0x14); config_bao_layer_m(bao, 0x4c, 0x20, 0x2c, 0x44, 0x00, 0x50, 0x00, 0x00, 1); /* stream size: 0x48? */ config_bao_layer_e(bao, 0x30, 0x00, 0x04, 0x08, 0x10); + config_bao_silence_f(bao, 0x1c); + bao->cfg.codec_map[0x02] = RAW_PSX; bao->cfg.codec_map[0x03] = UBI_IMA; bao->cfg.codec_map[0x04] = FMT_OGG; @@ -1606,7 +1744,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { case 0x001F0011: /* Naruto: The Broken Bond (X360)-package */ case 0x0022000D: /* Just Dance (Wii)-package */ case 0x0022001B: /* Prince of Persia: The Forgotten Sands (Wii)-package */ - config_bao_entry(bao, 0xA4, 0x28); + config_bao_entry(bao, 0xA4, 0x28); /* PC/Wii: 0xA8 */ config_bao_audio_b(bao, 0x08, 0x1c, 0x28, 0x34, 1, 1); config_bao_audio_m(bao, 0x44, 0x4c, 0x54, 0x5c, 0x64, 0x74); /* cues: 0x68, 0x6c */ @@ -1616,6 +1754,8 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { config_bao_layer_m(bao, 0x00, 0x20, 0x2c, 0x44, 0x4c, 0x50, 0x54, 0x58, 1); /* 0x1c: id-like, 0x3c: prefetch flag? */ config_bao_layer_e(bao, 0x28, 0x00, 0x04, 0x08, 0x10); + config_bao_silence_f(bao, 0x1c); + bao->cfg.codec_map[0x01] = RAW_PCM; bao->cfg.codec_map[0x03] = UBI_IMA; bao->cfg.codec_map[0x04] = FMT_OGG; @@ -1628,7 +1768,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { case 0x00220015: /* James Cameron's Avatar: The Game (PSP)-package */ case 0x0022001E: /* Prince of Persia: The Forgotten Sands (PSP)-package */ - config_bao_entry(bao, 0x84, 0x28); + config_bao_entry(bao, 0x84, 0x28); /* PSP: 0x84 */ config_bao_audio_b(bao, 0x08, 0x1c, 0x20, 0x20, (1 << 2), (1 << 5)); /* (1 << 4): prefetch flag? */ config_bao_audio_m(bao, 0x28, 0x30, 0x38, 0x40, 0x48, 0x58); @@ -1642,7 +1782,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { return 1; case 0x00230008: /* Splinter Cell: Conviction (X360/PC)-package */ - config_bao_entry(bao, 0xB4, 0x28); + config_bao_entry(bao, 0xB4, 0x28); /* PC: 0xB8, X360: 0xB4 */ config_bao_audio_b(bao, 0x08, 0x24, 0x38, 0x44, 1, 1); config_bao_audio_m(bao, 0x54, 0x5c, 0x64, 0x6c, 0x74, 0x84); @@ -1663,17 +1803,24 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { case 0x00250108: /* Scott Pilgrim vs the World (PS3/X360)-package */ case 0x0025010A: /* Prince of Persia: The Forgotten Sands (PS3/X360)-atomic-forge */ case 0x00250119: /* Shaun White Skateboarding (Wii)-package */ - case 0x0025011D: /* Shaun White Skateboarding (PS3)-atomic-forge */ - config_bao_entry(bao, 0xB4, 0x28); + case 0x0025011D: /* Shaun White Skateboarding (PC/PS3)-atomic-forge */ + config_bao_entry(bao, 0xB4, 0x28); /* PC: 0xB0, PS3/X360: 0xB4, Wii: 0xB8 */ + + if (bao->version == 0x0025011D) + bao->cfg.header_less_le_flag = 1; config_bao_audio_b(bao, 0x08, 0x24, 0x2c, 0x38, 1, 1); config_bao_audio_m(bao, 0x48, 0x50, 0x58, 0x60, 0x68, 0x78); + bao->cfg.audio_interleave = 0x10; config_bao_sequence(bao, 0x34, 0x28, 0x24, 0x14); config_bao_layer_m(bao, 0x00, 0x28, 0x30, 0x48, 0x50, 0x54, 0x58, 0x5c, 1); /* 0x24: id-like */ config_bao_layer_e(bao, 0x30, 0x00, 0x04, 0x08, 0x18); //todo some SPvsTW layers look like should loop (0x30 flag?) + //todo some POP layers have different sample rates (ambience) + + config_bao_silence_f(bao, 0x24); bao->cfg.codec_map[0x01] = RAW_PCM; bao->cfg.codec_map[0x02] = UBI_IMA; @@ -1687,6 +1834,29 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { bao->cfg.file_type = UBI_FORGE_b; return 1; + case 0x00280303: /* Tom Clancy's Ghost Recon Future Soldier (PC/PS3)-pk */ + config_bao_entry(bao, 0xBC, 0x28); /* PC/PS3: 0xBC */ + + config_bao_audio_b(bao, 0x08, 0x38, 0x3c, 0x48, 1, 1); + config_bao_audio_m(bao, 0x54, 0x5c, 0x64, 0x6c, 0x74, 0x80); + + config_bao_sequence(bao, 0x48, 0x3c, 0x38, 0x14); + + config_bao_layer_m(bao, 0x00, 0x3c, 0x44, 0x58, 0x60, 0x64, 0x00, 0x00, 1); + config_bao_layer_e(bao, 0x2c, 0x00, 0x04, 0x08, 0x1c); + bao->cfg.layer_ignore_error = 1; //todo some layer sample rates don't match + //todo some files have strange prefetch+stream of same size (2 segments?), ex. CEND_30_VOX.lpk + + config_bao_silence_f(bao, 0x38); + + bao->cfg.codec_map[0x01] = RAW_PCM; + bao->cfg.codec_map[0x02] = UBI_IMA; /* v6 */ + bao->cfg.codec_map[0x04] = FMT_OGG; + bao->cfg.codec_map[0x07] = RAW_AT3; //todo some layers use AT3_105 + + bao->cfg.file_type = UBI_FORGE_b; + return 1; + case 0x001B0200: /* Beowulf (PS3)-atomic-bin+fat */ /* same as 0x001B0100 except: * - base 0xA0, skip 0x24, name style %08x (.bao/sbao?) */ @@ -1694,6 +1864,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { /* same as 0x001B0100 except: * - base 0xA0, skip 0x24, name style %08x.bao (not .sbao?) */ case 0x001D0A00: /* Shaun White Snowboarding (PSP)-atomic-opal */ + case 0x00220017: /* Avatar (PS3)-atomic/spk */ case 0x00220018: /* Avatar (PS3)-atomic/spk */ case 0x00260102: /* Prince of Persia Trilogy HD (PS3)-package-gear */ /* similar to 0x00250108 but most values are moved +4 @@ -1704,6 +1875,8 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) { * - base varies per type (0xF0=audio), skip 0x20 * - 0x74: type, 0x78: channels, 0x7c: sample rate, 0x80: num_samples * - 0x94: stream id? 0x9C: extra size */ + case 0x002A0300: /* Watch Dogs (Wii U) */ + /* similar to SC:B */ default: /* others possibly using BAO: Just Dance, Watch_Dogs, Far Cry Primal, Far Cry 4 */ VGM_LOG("UBI BAO: unknown BAO version %08x\n", bao->version); return 0; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao_streamfile.h index f72298c86..071dab6bb 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao_streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_bao_streamfile.h @@ -5,7 +5,7 @@ #include "ubi_sb_streamfile.h" static STREAMFILE* setup_ubi_bao_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, int layer_count, int big_endian) { - return setup_ubi_sb_streamfile(streamFile, stream_offset, stream_size, layer_number, layer_count, big_endian); + return setup_ubi_sb_streamfile(streamFile, stream_offset, stream_size, layer_number, layer_count, big_endian, 0); } #endif /* _UBI_BAO_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_jade.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_jade.c index 054c87142..35f44bfb9 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_jade.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_jade.c @@ -233,7 +233,7 @@ VGMSTREAM * init_vgmstream_ubi_jade(STREAMFILE *streamFile) { temp_streamFile = setup_subfile_streamfile(streamFile, start_offset, data_size, "msf"); if (!temp_streamFile) goto fail; - temp_vgmstream = init_vgmstream_ps3_msf(temp_streamFile); + temp_vgmstream = init_vgmstream_msf(temp_streamFile); close_streamfile(temp_streamFile); if (!temp_vgmstream) goto fail; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_raki.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_raki.c index bf377de0e..403900b21 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_raki.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_raki.c @@ -103,20 +103,20 @@ VGMSTREAM * init_vgmstream_ubi_raki(STREAMFILE *streamFile) { case 0x4361666561647063: /* "Cafeadpc" (Cafe = WiiU) */ /* chunks: "datS" (stereo), "datL" (mono or full interleave), "datR" (full interleave), "data" equivalents */ vgmstream->coding_type = coding_NGC_DSP; - vgmstream->layout_type = channel_count==1 ? layout_none : layout_interleave; - vgmstream->interleave_block_size = 8; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x8; /* we need to know if the file uses "datL" and is full-interleave */ if (channel_count > 1) { - off_t chunk_off = offset+ 0x20 + 0xc; /* after "fmt" */ - while (chunk_off < header_size) { - if (read_32bitBE(chunk_off,streamFile) == 0x6461744C) { /*"datL" found */ - size_t chunk_size = read_32bit(chunk_off+0x8,streamFile); + off_t chunk_offset = offset+ 0x20 + 0xc; /* after "fmt" */ + while (chunk_offset < header_size) { + if (read_32bitBE(chunk_offset,streamFile) == 0x6461744C) { /* "datL" found */ + size_t chunk_size = read_32bit(chunk_offset+0x8,streamFile); data_size = chunk_size * channel_count; /* to avoid counting the "datR" chunk */ vgmstream->interleave_block_size = (4+4) + chunk_size; /* don't forget to skip the "datR"+size chunk */ break; } - chunk_off += 0xc; + chunk_offset += 0xc; } /* not found? probably "datS" (regular stereo interleave) */ @@ -132,6 +132,38 @@ VGMSTREAM * init_vgmstream_ubi_raki(STREAMFILE *streamFile) { vgmstream->num_samples = dsp_bytes_to_samples(data_size, channel_count); break; + case 0x4354520061647063: /* "CTR\0adpc" (Citrus = 3DS) */ + /* chunks: "dspL" (CWAV-L header), "dspR" (CWAV-R header), "cwav" ("data" equivalent) */ + vgmstream->coding_type = coding_NGC_DSP; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x8; + + /* reading could be improved but should work with some luck since most values are semi-fixed */ + if (channel_count > 1) { + /* find "dspL" pointing to "CWAV" header and read coefs (separate from data at start_offset) */ + off_t chunk_offset = offset+ 0x20 + 0xc; /* after "fmt" */ + while (chunk_offset < header_size) { + if (read_32bitBE(chunk_offset,streamFile) == 0x6473704C) { /* "dspL" found */ + off_t cwav_offset = read_32bit(chunk_offset+0x4,streamFile); + size_t cwav_size = read_32bit(chunk_offset+0x8,streamFile); + + dsp_read_coefs(vgmstream,streamFile, cwav_offset + 0x7c, cwav_size, big_endian); + break; + } + chunk_offset += 0xc; + } + } + else { + /* CWAV at start (a full CWAV, unlike the above) */ + dsp_read_coefs(vgmstream,streamFile, start_offset + 0x7c, 0x00, big_endian); + start_offset += 0xE0; + data_size = get_streamfile_size(streamFile) - start_offset; + } + + vgmstream->num_samples = dsp_bytes_to_samples(data_size, channel_count); + break; + + #ifdef VGM_USE_MPEG case 0x505333206D703320: { /* "PS3 mp3 " */ /* chunks: "MARK" (optional seek table), "STRG" (optional description), "Msf " ("data" equivalent) */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c index 296c5b6bf..46a5f5227 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb.c @@ -5,9 +5,9 @@ #define SB_MAX_LAYER_COUNT 16 /* arbitrary max */ -#define SB_MAX_CHAIN_COUNT 64 /* arbitrary max */ +#define SB_MAX_CHAIN_COUNT 256 /* +150 exist in Tonic Trouble */ -typedef enum { UBI_IMA, UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_AT3, FMT_XMA1, RAW_XMA1, FMT_OGG, FMT_CWAV } ubi_sb_codec; +typedef enum { UBI_IMA, UBI_ADPCM, RAW_PCM, RAW_PSX, RAW_DSP, RAW_XBOX, FMT_VAG, FMT_AT3, RAW_AT3, FMT_XMA1, RAW_XMA1, FMT_OGG, FMT_CWAV, FMT_APM } ubi_sb_codec; typedef enum { UBI_PC, UBI_PS2, UBI_XBOX, UBI_GC, UBI_X360, UBI_PSP, UBI_PS3, UBI_WII, UBI_3DS } ubi_sb_platform; typedef enum { UBI_NONE = 0, UBI_AUDIO, UBI_LAYER, UBI_SEQUENCE, UBI_SILENCE } ubi_sb_type; @@ -38,6 +38,7 @@ typedef struct { int audio_group_and; int audio_has_internal_names; size_t audio_interleave; + int audio_fix_psx_samples; off_t sequence_extra_offset; off_t sequence_sequence_loop; @@ -57,10 +58,16 @@ typedef struct { off_t layer_stream_type; off_t layer_num_samples; size_t layer_entry_size; + size_t layer_hijack; off_t silence_duration_int; off_t silence_duration_float; + off_t random_extra_offset; + off_t random_sequence_count; + size_t random_entry_size; + int random_percent_int; + int is_padded_section1_offset; int is_padded_section2_offset; int is_padded_section3_offset; @@ -89,11 +96,13 @@ typedef struct { uint32_t map_zero; off_t map_offset; off_t map_size; - char map_name[0x24]; + char map_name[0x28]; uint32_t map_unknown; /* SB info (some values are derived depending if it's standard sbX or map sbX) */ + int is_bank; int is_map; + int is_bnm; uint32_t version; /* 16b+16b major+minor version */ uint32_t version_empty; /* map sbX versions are empty */ /* events (often share header_id/type with some descriptors, @@ -137,21 +146,25 @@ typedef struct { off_t xma_header_offset; /* some XMA have extra header stuff */ int layer_count; /* number of layers in a layer type */ + int layer_channels[SB_MAX_LAYER_COUNT]; int sequence_count; /* number of segments in a sequence type */ int sequence_chain[SB_MAX_CHAIN_COUNT]; /* sequence of entry numbers */ + int sequence_banks[SB_MAX_CHAIN_COUNT]; /* sequence of bnk bank numbers */ + int sequence_multibank; /* info flag */ int sequence_loop; /* chain index to loop */ int sequence_single; /* if que sequence plays once (loops by default) */ float duration; /* silence duration */ int is_external; /* stream is in a external file */ - char resource_name[0x24]; /* filename to the external stream, or internal stream info for some games */ + char resource_name[0x28]; /* filename to the external stream, or internal stream info for some games */ char readable_name[255]; /* final subsong name */ int types[16]; /* counts each header types, for debugging */ int allowed_types[16]; } ubi_sb_header; +static int parse_bnm_header(ubi_sb_header * sb, STREAMFILE *streamFile); static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset, int index); static int parse_sb(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subsong); static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* streamTest, STREAMFILE *streamFile); @@ -191,7 +204,7 @@ VGMSTREAM * init_vgmstream_ubi_sb(STREAMFILE *streamFile) { /* SB HEADER */ /* SBx layout: header, section1, section2, extra section, section3, data (all except header can be null) */ - sb.is_map = 0; + sb.is_bank = 1; sb.version = read_32bit(0x00, streamFile); sb.section1_num = read_32bit(0x04, streamFile); sb.section2_num = read_32bit(0x08, streamFile); @@ -340,18 +353,85 @@ fail: return NULL; } -#if 0 -/* .BNM - proto-sbX with map style format [Donald Duck: Goin' Quackers (PC), Tonic Trouble (PC)] */ -VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE *streamFile) { - /* v0x00000000, header is somewhat like a map-style bank (offsets + sizes) but sectionX/3 fields - * are fixed/reserved (unused?). Header entry sizes and config looks the same as early games. - * Main codecs are 01=RAW_PCM and 04=FMT_APM (.apm if external, possibly Ubi IMA v0 but has a full header). - * The sound engine doesn't seem to be named DARE yet. - */ +/* .BNM - proto-sbX with map style format [Rayman 2 (PC), Donald Duck: Goin' Quackers (PC), Tonic Trouble (PC)] */ +VGMSTREAM * init_vgmstream_ubi_bnm(STREAMFILE *streamFile) { + VGMSTREAM* vgmstream = NULL; + STREAMFILE *streamTest = NULL; + ubi_sb_header sb = {0}; + int target_subsong = streamFile->stream_index; + + if (target_subsong <= 0) target_subsong = 1; + + + /* checks */ + if (!check_extensions(streamFile, "bnm")) + goto fail; + + /* v0, header is somewhat like a map-style bank (offsets + sizes) but sectionX/3 fields are + * fixed/reserved (unused?). Header entry sizes and config works the same, and type numbers are + * slightly different, but otherwise pretty much the same engine (not named DARE yet). Curiously + * it may stream RIFF .wav (stream_offset pointing to "data"), and also .raw (PCM) or .apm IMA. */ + + /* use smaller header buffer for performance */ + streamTest = reopen_streamfile(streamFile, 0x100); + if (!streamTest) goto fail; + + if (!parse_bnm_header(&sb, streamTest)) + goto fail; + + if (!parse_sb(&sb, streamTest, target_subsong)) + goto fail; + + /* CREATE VGMSTREAM */ + vgmstream = init_vgmstream_ubi_sb_header(&sb, streamTest, streamFile); + close_streamfile(streamTest); + return vgmstream; + +fail: + close_streamfile(streamTest); return NULL; } -#endif + +static int parse_bnm_header(ubi_sb_header * sb, STREAMFILE *streamFile) { + int32_t(*read_32bit)(off_t, STREAMFILE*) = NULL; + + /* PLATFORM DETECTION */ + sb->platform = UBI_PC; + read_32bit = sb->big_endian ? read_32bitBE : read_32bitLE; + + /* SB HEADER */ + /* SBx layout: header, section1, section2, extra section, section3, data (all except header can be null) */ + sb->is_bnm = 1; + sb->version = read_32bit(0x00, streamFile); + sb->section1_offset = read_32bit(0x04, streamFile); + sb->section1_num = read_32bit(0x08, streamFile); + sb->section2_offset = read_32bit(0x0c, streamFile); + sb->section2_num = read_32bit(0x10, streamFile); + /* next are data start offset x3 + data size offset x3 */ + sb->section3_offset = read_32bit(0x14, streamFile); + sb->section3_num = 0; + + if (!config_sb_version(sb, streamFile)) + goto fail; + + sb->sectionX_offset = sb->section2_offset + sb->section2_num * sb->cfg.section2_entry_size; + sb->sectionX_size = sb->section3_offset - sb->sectionX_offset; + + return 1; +fail: + return 0; +} + +static int is_bnm_other_bank(STREAMFILE *streamFile, int bank_number) { + char current_name[PATH_LIMIT]; + char bank_name[255]; + + get_streamfile_filename(streamFile, current_name, PATH_LIMIT); + sprintf(bank_name, "Bnk_%i.bnm", bank_number); + + return strcmp(current_name, bank_name) != 0; +} #if 0 /* .BLK - maps in separate .blk chunks [Donald Duck: Goin' Quackers (PS2), The Jungle Book Rhythm N'Groove (PS2)] */ @@ -396,15 +476,15 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str break; case UBI_ADPCM: - /* todo custom Ubi 4/6-bit ADPCM (4b for music/stereo and 6b for voices?) - * - ~0x30: block-like header (some versions may not have it). - * Seems to contain frame size, channels, sometimes sample rate/samples/etc. - * - 0x34 per channel: channel header, starts with 0x02 - * - 0x600: frame data (unknown interleave) - * - 0x02: ADPCM hist? - * - * frame data looks decoded in two passes (first may expand nibble somehow, other - * seems to calculate diffs) + /* todo custom Ubi 4/6-bit ADPCM (4b for music/stereo and 6b for voices) + * - ~0x30: stream header (varies with versions). + * contains frame sizes (normal/last), channels, sometimes sample rate/samples/etc. + * - 0x34 per channel: channel header, starts with 0x02, contains ADPCM info + * - 0x602/902: frame data divided into 2 chunks (with a padding byte) + * for each chunk frame data is read as LE int32, then divided into codes + * (some 6b span two int32). stereo interleaves 4 codes per channel. + * data is decoded using 3 tables and ops to adjust sample and calculate next step + * (6b decoding is much simpler than 4b, that applies more extra ops). * * used in: * - Batman: Vengeance (PC) @@ -420,6 +500,12 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str vgmstream->coding_type = coding_PCM16LE; /* always LE */ vgmstream->layout_type = layout_interleave; vgmstream->interleave_block_size = 0x02; + + if (vgmstream->num_samples == 0) { /* happens in .bnm */ + //todo with external wav streams stream_size may be off? + vgmstream->num_samples = pcm_bytes_to_samples(sb->stream_size, sb->channels, 16); + vgmstream->loop_end_sample = vgmstream->num_samples; + } break; case RAW_PSX: @@ -428,10 +514,20 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str vgmstream->interleave_block_size = (sb->cfg.audio_interleave) ? sb->cfg.audio_interleave : sb->stream_size / sb->channels; + if (vgmstream->num_samples == 0) { /* early PS2 games may not set it for internal streams */ vgmstream->num_samples = ps_bytes_to_samples(sb->stream_size, sb->channels); vgmstream->loop_end_sample = vgmstream->num_samples; } + + /* late PS3 SBs have double sample count here for who knows why + * (loops or not, PS-ADPCM only, possibly only when using codec 0x02 for RAW_PSX) */ + if (sb->cfg.audio_fix_psx_samples) { + vgmstream->num_samples /= sb->channels; + vgmstream->loop_start_sample /= sb->channels; + vgmstream->loop_end_sample /= sb->channels; + } + break; case RAW_XBOX: @@ -489,7 +585,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str block_size = 0x98 * sb->channels; joint_stereo = 0; - encoder_delay = 0x00; /* TODO: this is incorrect */ + encoder_delay = 0x00; /* TODO: this is may be incorrect */ bytes = ffmpeg_make_riff_atrac3(buf, 0x100, sb->num_samples, sb->stream_size, sb->channels, sb->sample_rate, block_size, joint_stereo, encoder_delay); ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb->stream_size); @@ -554,6 +650,9 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str /* get XMA header from extra section */ chunk_size = 0x20; header_offset = sb->xma_header_offset; + if (header_offset == 0) + header_offset = sb->extra_offset; + bytes = ffmpeg_make_riff_xma_from_fmt_chunk(buf, 0x100, header_offset, chunk_size, sb->stream_size, streamHead, 1); ffmpeg_data = init_ffmpeg_header_offset(streamData, buf, bytes, start_offset, sb->stream_size); @@ -587,6 +686,53 @@ static VGMSTREAM * init_vgmstream_ubi_sb_base(ubi_sb_header *sb, STREAMFILE *str start_offset += 0xe0; /* skip CWAV header */ break; + case FMT_APM: + /* APM is a full format though most fields are repeated from .bnm + * (info from https://github.com/Synthesis/ray2get) + * 0x00(2): format tag (0x2000 for Ubisoft ADPCM) + * 0x02(2): channels + * 0x04(4): sample rate + * 0x08(4): byte rate? PCM samples? + * 0x0C(2): block align + * 0x0E(2): bits per sample + * 0x10(4): header size + * 0x14(4): "vs12" + * 0x18(4): file size + * 0x1C(4): nibble size + * 0x20(4): -1? + * 0x24(4): 0? + * 0x28(4): high/low nibble flag (when loaded in memory) + * 0x2C(N): ADPCM info per channel, last to first + * - 0x00(4): ADPCM hist + * - 0x04(4): ADPCM step index + * - 0x08(4): copy of ADPCM data (after interleave, ex. R from data + 0x01) + * 0x60(4): "DATA" + * 0x64(N): ADPCM data + */ + + vgmstream->coding_type = coding_DVI_IMA_int; + vgmstream->layout_type = layout_interleave; + vgmstream->interleave_block_size = 0x01; + + /* read initial hist (last to first) */ + { + int i; + for (i = 0; i < sb->channels; i++) { + vgmstream->ch[i].adpcm_history1_32 = read_32bitLE(start_offset + 0x2c + 0x0c*(sb->channels - 1 - i) + 0x00, streamData); + vgmstream->ch[i].adpcm_step_index = read_32bitLE(start_offset + 0x2c + 0x0c*(sb->channels - 1 - i) + 0x04, streamData); + } + } + //todo supposedly APM IMA removes lower 3b after assigning step, but wave looks a bit off (Rayman 2 only?): + // ...; step = adpcm_table[step_index]; delta = (step >> 3); step &= (~7); ... + + start_offset += 0x64; /* skip APM header (may be internal or external) */ + + if (vgmstream->num_samples == 0) { + vgmstream->num_samples = ima_bytes_to_samples(sb->stream_size - 0x64, sb->channels); + vgmstream->loop_end_sample = vgmstream->num_samples; + } + break; + default: VGM_LOG("UBI SB: unknown codec\n"); goto fail; @@ -639,7 +785,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st STREAMFILE* temp_streamFile = NULL; STREAMFILE *streamData = NULL; size_t full_stream_size = sb->stream_size; - int i; + int i, total_channels = 0; /* open external stream if needed */ if (sb->is_external) { @@ -660,10 +806,12 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st /* open all layers and mix */ for (i = 0; i < sb->layer_count; i++) { /* prepare streamfile from a single layer section */ - temp_streamFile = setup_ubi_sb_streamfile(streamData, sb->stream_offset, full_stream_size, i, sb->layer_count, sb->big_endian); + temp_streamFile = setup_ubi_sb_streamfile(streamData, sb->stream_offset, full_stream_size, i, sb->layer_count, sb->big_endian, sb->cfg.layer_hijack); if (!temp_streamFile) goto fail; sb->stream_size = get_streamfile_size(temp_streamFile); + sb->channels = sb->layer_channels[i]; + total_channels += sb->layer_channels[i]; /* build the layer VGMSTREAM (standard sb with custom streamfile) */ data->layers[i] = init_vgmstream_ubi_sb_base(sb, streamTest, temp_streamFile, 0x00); @@ -678,7 +826,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_layer(ubi_sb_header *sb, STREAMFILE *st /* build the base VGMSTREAM */ - vgmstream = allocate_vgmstream(sb->channels * sb->layer_count, sb->loop_flag); + vgmstream = allocate_vgmstream(total_channels, sb->loop_flag); if (!vgmstream) goto fail; vgmstream->meta_type = meta_UBI_SB; @@ -711,6 +859,8 @@ static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE VGMSTREAM * vgmstream = NULL; segmented_layout_data* data = NULL; int i; + STREAMFILE *streamBank = streamTest; + //todo optimization: open streamData once / only if new name (doesn't change 99% of the time) @@ -723,32 +873,60 @@ static VGMSTREAM * init_vgmstream_ubi_sb_sequence(ubi_sb_header *sb, STREAMFILE /* open all segments and mix */ for (i = 0; i < sb->sequence_count; i++) { - ubi_sb_header temp_sb = *sb; /* memcpy'ed */ + ubi_sb_header temp_sb = {0}; + off_t entry_offset; int entry_index = sb->sequence_chain[i]; - off_t entry_offset = sb->section2_offset + sb->cfg.section2_entry_size * entry_index; + + + /* bnm sequences may use to entries from other banks, do some voodoo */ + if (sb->is_bnm) { + /* see if *current* bank has changed (may use a different bank N times) */ + if (is_bnm_other_bank(streamBank, sb->sequence_banks[i])) { + char bank_name[255]; + sprintf(bank_name, "Bnk_%i.bnm", sb->sequence_banks[i]); + + if (streamBank != streamTest) + close_streamfile(streamBank); + + streamBank = open_streamfile_by_filename(streamFile, bank_name); + if (!streamBank) goto fail; + } + + /* re-parse the thing */ + if (!parse_bnm_header(&temp_sb, streamBank)) + goto fail; + temp_sb.total_subsongs = 1; /* eh... just to keep parse_header happy */ + } + else { + temp_sb = *sb; /* memcpy'ed */ + } /* parse expected entry */ - if (!parse_header(&temp_sb, streamTest, entry_offset, entry_index)) + entry_offset = temp_sb.section2_offset + temp_sb.cfg.section2_entry_size * entry_index; + if (!parse_header(&temp_sb, streamBank, entry_offset, entry_index)) goto fail; if (temp_sb.type == UBI_NONE || temp_sb.type == UBI_SEQUENCE) { - VGM_LOG("UBI SB: unexpected sequence entry type at %x\n", (uint32_t)entry_offset); - goto fail; /* technically ok but too much recursiveness? */ + VGM_LOG("UBI SB: unexpected sequence %i entry type at %x\n", i, (uint32_t)entry_offset); + goto fail; /* not seen, technically ok but too much recursiveness? */ } /* build the layer VGMSTREAM (current sb entry config) */ - data->segments[i] = init_vgmstream_ubi_sb_header(&temp_sb, streamTest, streamFile); + data->segments[i] = init_vgmstream_ubi_sb_header(&temp_sb, streamBank, streamFile); if (!data->segments[i]) goto fail; if (i == sb->sequence_loop) sb->loop_start = sb->num_samples; sb->num_samples += data->segments[i]->num_samples; - /* save current (silences don't have values, so this unsures they know when memcpy'ed) */ + /* save current (silences don't have values, so this ensures they know later, when memcpy'ed) */ sb->channels = temp_sb.channels; sb->sample_rate = temp_sb.sample_rate; } + if (streamBank != streamTest) + close_streamfile(streamBank); + if (!setup_layout_segmented(data)) goto fail; @@ -775,6 +953,8 @@ fail: close_vgmstream(vgmstream); else free_layout_segmented(data); + if (streamBank != streamTest) + close_streamfile(streamBank); return NULL; } @@ -829,7 +1009,7 @@ static VGMSTREAM * init_vgmstream_ubi_sb_silence(ubi_sb_header *sb, STREAMFILE * vgmstream->meta_type = meta_UBI_SB; vgmstream->sample_rate = sample_rate; - vgmstream->num_samples = sb->duration * sample_rate; + vgmstream->num_samples = (int32_t)(sb->duration * (float)sample_rate); vgmstream->num_streams = sb->total_subsongs; vgmstream->stream_size = vgmstream->num_samples * channel_count * 0x02; /* PCM size */ @@ -860,7 +1040,6 @@ static VGMSTREAM * init_vgmstream_ubi_sb_header(ubi_sb_header *sb, STREAMFILE* s //;VGM_LOG("UBI SB: target at %x + %x, extra=%x, name=%s, g=%i, t=%i\n", // (uint32_t)sb->header_offset, sb->cfg.section2_entry_size, (uint32_t)sb->extra_offset, sb->resource_name, sb->group_id, sb->stream_type); - //;VGM_LOG("UBI SB: stream offset=%x, size=%x, name=%s\n", (uint32_t)sb->stream_offset, sb->stream_size, sb->is_external ? sb->resource_name : "internal" ); switch(sb->type) { @@ -905,10 +1084,18 @@ static void build_readable_name(char * buf, size_t buf_size, ubi_sb_header * sb) int index; /* config */ - if (sb->is_map) + if (sb->is_map) { grp_name = sb->map_name; - else + } + else if (sb->is_bnm) { + if (sb->sequence_multibank) + grp_name = "bnm-multi"; + else + grp_name = "bnm"; + } + else { grp_name = "bank"; + } id = sb->header_id; type = sb->header_type; if (sb->is_map) @@ -1049,7 +1236,7 @@ static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* str sb->sequence_count = read_32bit(offset + sb->cfg.sequence_sequence_count, streamFile); if (sb->sequence_count > SB_MAX_CHAIN_COUNT) { - VGM_LOG("Ubi SB: incorrect layer count\n"); + VGM_LOG("Ubi SB: incorrect sequence count %i vs %i\n", sb->sequence_count, SB_MAX_CHAIN_COUNT); goto fail; } @@ -1058,15 +1245,30 @@ static int parse_type_sequence(ubi_sb_header * sb, off_t offset, STREAMFILE* str for (i = 0; i < sb->sequence_count; i++) { uint32_t entry_number = (uint32_t)read_32bit(table_offset+sb->cfg.sequence_entry_number, streamFile); + /* bnm sequences may refer to entries from different banks, whee */ + if (sb->is_bnm) { + int16_t bank_number = (entry_number >> 16) & 0xFFFF; + entry_number = (entry_number >> 00) & 0xFFFF; + + //;VGM_LOG("UBI SB: bnm sequence entry=%i, bank=%i\n", entry_number, bank_number); + sb->sequence_banks[i] = bank_number; + + /* info flag, does bank number point to another file? */ + if (!sb->sequence_multibank) { + sb->sequence_multibank = is_bnm_other_bank(streamFile, bank_number); + } + } + else { + entry_number = entry_number & 0x3FFFFFFF; + if (entry_number > sb->section2_num) { + VGM_LOG("UBI SB: chain with wrong entry %i vs %i at %x\n", entry_number, sb->section2_num, (uint32_t)sb->extra_offset); + goto fail; + } + } + /* some sequences have an upper bit (2 bits in Donald Duck voices) for some reason */ //;VGM_ASSERT_ONCE(entry_number & 0xC0000000, "UBI SB: sequence bit entry found at %x\n", (uint32_t)sb->extra_offset); - entry_number = entry_number & 0x3FFFFFFF; - if (entry_number > sb->section2_num) { - VGM_LOG("UBI SB: chain with wrong entry %i vs %i at %x\n", entry_number, sb->section2_num, (uint32_t)sb->extra_offset); - goto fail; - } - sb->sequence_chain[i] = entry_number; table_offset += sb->cfg.sequence_entry_size; @@ -1107,9 +1309,9 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream /* get 1st layer header in extra table and validate all headers match */ table_offset = sb->extra_offset; - sb->channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */ - (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) : - (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile); + //sb->channels = (sb->cfg.layer_channels % 4) ? /* non-aligned offset is always 16b */ + // (uint16_t)read_16bit(table_offset + sb->cfg.layer_channels, streamFile) : + // (uint32_t)read_32bit(table_offset + sb->cfg.layer_channels, streamFile); sb->sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, streamFile); sb->stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, streamFile); sb->num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile); @@ -1121,17 +1323,16 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream int sample_rate = read_32bit(table_offset + sb->cfg.layer_sample_rate, streamFile); int stream_type = read_32bit(table_offset + sb->cfg.layer_stream_type, streamFile); int num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile); - if (sb->channels != channels || sb->sample_rate != sample_rate || sb->stream_type != stream_type) { - VGM_LOG("Ubi SB: %i layer headers don't match at %x\n", sb->layer_count, (uint32_t)table_offset); - if (sb->cfg.ignore_layer_error) { - sb->layer_count = 1; - break; - } - - goto fail; + if (sb->sample_rate != sample_rate || sb->stream_type != stream_type) { + VGM_LOG("Ubi SB: %i layer headers don't match at %x > %x\n", sb->layer_count, (uint32_t)offset, (uint32_t)table_offset); + if (!sb->cfg.ignore_layer_error) /* layers of different rates happens sometimes */ + goto fail; } + /* uncommonly channels may vary per layer [Brothers in Arms 2 (PS2) ex. MP_B01_NL.SB1] */ + sb->layer_channels[i] = channels; + /* can be +-1 */ if (sb->num_samples != num_samples && sb->num_samples + 1 == num_samples) { sb->num_samples -= 1; @@ -1186,6 +1387,57 @@ fail: return 0; } +// todo improve, only used in bnm sequences as sequence end (and may point to another bnm) +static int parse_type_random(ubi_sb_header * sb, off_t offset, STREAMFILE* streamFile) { + int32_t (*read_32bit)(off_t,STREAMFILE*) = sb->big_endian ? read_32bitBE : read_32bitLE; + + off_t sb_extra_offset, table_offset; + int i, sb_sequence_count; + + /* sequence chain */ + if (sb->cfg.random_entry_size == 0) { + VGM_LOG("Ubi SB: random entry size not configured at %x\n", (uint32_t)offset); + goto fail; + } + + sb_extra_offset = read_32bit(offset + sb->cfg.random_extra_offset, streamFile) + sb->sectionX_offset; + sb_sequence_count = read_32bit(offset + sb->cfg.random_sequence_count, streamFile); + + + /* get chain in extra table */ + table_offset = sb_extra_offset; + for (i = 0; i < sb_sequence_count; i++) { + uint32_t entry_number = (uint32_t)read_32bit(table_offset+0x00, streamFile); + //uint32_t entry_chance = (uint32_t)read_32bit(table_offset+0x04, streamFile); + + if (sb->is_bnm) { + int16_t bank_number = (entry_number >> 16) & 0xFFFF; + entry_number = (entry_number >> 00) & 0xFFFF; + + ;VGM_LOG("UBI SB: bnm sequence entry=%i, bank=%i\n", entry_number, bank_number); + //sb->sequence_banks[i] = bank_number; + + /* not seen */ + if (is_bnm_other_bank(streamFile, bank_number)) { + VGM_LOG("UBI SB: random in other bank\n"); + goto fail; + } + } + + //todo make rand or stuff (old chance: int from 0 to 0x10000, new: float from 0.0 to 1.0) + { //if (entry_chance == ...) + off_t entry_offset = sb->section2_offset + sb->cfg.section2_entry_size * entry_number; + return parse_type_audio(sb, entry_offset, streamFile); + } + + table_offset += sb->cfg.random_entry_size; + } + + return 1; +fail: + return 0; +} + /* find actual codec from type (as different games' stream_type can overlap) */ static int parse_stream_codec(ubi_sb_header * sb) { @@ -1199,6 +1451,14 @@ static int parse_stream_codec(ubi_sb_header * sb) { sb->stream_type = 0; /* probably ignored for non-stream types */ } + /* Batman: Rise of Sin Tzu (Xbox) + * (similar but also for some streams, maybe uses a 'hardware flag' possibly at 0x20?) */ + if ((sb->version == 0x000A0003 && sb->platform == UBI_XBOX) && + (!sb->is_external || strncmp(sb->resource_name, "STREAMHW.", 9) == 0)) { + sb->stream_type = 0; + } + + /* guess codec */ switch (sb->stream_type) { case 0x00: /* platform default (rarely external) */ @@ -1277,6 +1537,10 @@ static int parse_stream_codec(ubi_sb_header * sb) { case 0x04: switch (sb->version) { + case 0x00000000: /* Rayman 2, Tonic Trouble */ + sb->codec = FMT_APM; + break; + case 0x00000007: /* Splinter Cell, Splinter Cell: Pandora Tomorrow */ sb->codec = UBI_IMA; break; @@ -1452,6 +1716,7 @@ static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset goto fail; break; case 0x05: + case 0x0b: case 0x0c: if (!parse_type_sequence(sb, offset, streamFile)) goto fail; @@ -1466,8 +1731,12 @@ static int parse_header(ubi_sb_header * sb, STREAMFILE *streamFile, off_t offset if (!parse_type_silence(sb, offset, streamFile)) goto fail; break; + case 0x0a: + if (!parse_type_random(sb, offset, streamFile)) + goto fail; + break; default: - VGM_LOG("UBI SB: unknown header type at %x\n", (uint32_t)offset); + VGM_LOG("UBI SB: unknown header type %x at %x\n", sb->header_type, (uint32_t)offset); goto fail; } @@ -1500,7 +1769,7 @@ static int parse_sb(ubi_sb_header * sb, STREAMFILE *streamFile, int target_subso /*header_id =*/ read_32bit(offset + 0x00, streamFile); /* forces buffer read */ header_type = read_32bit(offset + 0x04, streamFile); - if (header_type <= 0x00 || header_type >= 0x10 || header_type == 0x09 || header_type == 0x0b) { + if (header_type <= 0x00 || header_type >= 0x10 || header_type == 0x09) { VGM_LOG("UBI SB: unknown type %x at %x\n", header_type, (uint32_t)offset); goto fail; } @@ -1649,6 +1918,10 @@ static void config_sb_sequence(ubi_sb_header * sb, off_t sequence_count, off_t e sb->cfg.sequence_sequence_count = sequence_count; sb->cfg.sequence_entry_size = entry_size; sb->cfg.sequence_entry_number = 0x00; + if (sb->is_bnm) { + sb->cfg.sequence_sequence_loop = sequence_count - 0x0c; + sb->cfg.sequence_sequence_single= sequence_count - 0x08; + } } static void config_sb_layer_hs(ubi_sb_header * sb, off_t layer_count, off_t stream_size, off_t stream_offset, off_t stream_name) { /* layer headers with stream name */ @@ -1681,9 +1954,17 @@ static void config_sb_silence_f(ubi_sb_header * sb, off_t duration) { sb->cfg.silence_duration_float = duration; } +static void config_sb_random_old(ubi_sb_header * sb, off_t sequence_count, off_t entry_size) { + sb->cfg.random_sequence_count = sequence_count; + sb->cfg.random_entry_size = entry_size; + sb->cfg.random_percent_int = 1; +} + static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { + int is_dino_pc = 0; int is_bia_ps2 = 0, is_biadd_psp = 0; int is_sc2_ps2_gc = 0; + int is_sc4_pc_online = 0; /* Most of the format varies with almost every game + platform (struct serialization?). * Support is configured case-by-case as offsets/order/fields only change slightly, @@ -1752,24 +2033,25 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { /* debug strings reference: * - TYPE_SAMPLE: should be 0x01 (also "sound resource") - * - TYPE_MULTITRACK: should be 0x06/0x0d (also "multilayer resource") + * - TYPE_MULTITRACK: should be 0x06/0x0d/0x0b (also "multilayer resource") * - TYPE_SILENCE: should be 0x08 * sequences may be "theme resource" - * "class descryptor" is referenced too. + * "class descriptor" is referenced too. * - * Possible type names from .bnm (.sb's predecessor): + * Type names from .bnm (.sb's predecessor): * 0: TYPE_INVALID * 1: TYPE_SAMPLE * 2: TYPE_MIDI * 3: TYPE_CDAUDIO - * 4: TYPE_SEQUENCE + * 4: TYPE_SEQUENCE (sfx chain?) * 5: TYPE_SWITCH_OLD * 6: TYPE_SPLIT * 7: TYPE_THEME_OLD * 8: TYPE_SWITCH * 9: TYPE_THEME_OLD2 * A: TYPE_RANDOM - * B: TYPE_THEME0 + * B: TYPE_THEME0 (sequence) + * (only 1, 4, A and B are known) */ /* All types may contain memory garbage, making it harder to identify fields (platforms @@ -1781,8 +2063,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { /* games <= 0x00100000 seem to use old types, rest new types */ - /* common */ - sb->cfg.resource_name_size = 0x24; /* maybe 0x20/0x28 for some but ok enough (null terminated) */ + /* maybe 0x20/0x24 for some but ok enough (null terminated) */ + sb->cfg.resource_name_size = 0x28; /* min for Brother in Arms 2 (PS2) */ /* represents map style (1=first, 2=mid, 3=latest) */ if (sb->version <= 0x00000007) @@ -1794,7 +2076,22 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { sb->cfg.map_entry_size = (sb->cfg.map_version < 2) ? 0x30 : 0x34; - if (sb->version <= 0x00000007) { + if (sb->version == 0x00000000 || sb->version == 0x00000200) { + sb->cfg.audio_stream_size = 0x0c; + sb->cfg.audio_stream_offset = 0x10; + //sb->cfg.audio_extra_offset = 0x10; + //sb->cfg.audio_extra_size = 0x0c; + + sb->cfg.sequence_extra_offset = 0x10; + //sb->cfg.sequence_extra_size = 0x0c; + + //sb->cfg.layer_extra_offset = 0x10; + //sb->cfg.layer_extra_size = 0x0c; + + sb->cfg.random_extra_offset = 0x10; + //sb->cfg.random_extra_size = 0x0c; + } + else if (sb->version <= 0x00000007) { sb->cfg.audio_stream_size = 0x0c; sb->cfg.audio_extra_offset = 0x10; sb->cfg.audio_stream_offset = 0x14; @@ -1820,6 +2117,10 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { sb->allowed_types[0x0d] = 1; //sb->allowed_types[0x08] = 1; /* only needed inside sequences */ //sb->allowed_types[0x0f] = 1; + if (sb->is_bnm) { + //sb->allowed_types[0x0a] = 1; /* only needed inside sequences */ + sb->allowed_types[0x0b] = 1; + } #if 0 { @@ -1835,8 +2136,47 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { } #endif + /* two configs with same id; use SND file as identifier */ + if (sb->version == 0x00000000 && sb->platform == UBI_PC) { + STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "Dino.lcb"); + if (streamTest) { + is_dino_pc = 1; + close_streamfile(streamTest); + } + } + + /* some files in Dinosaur */ + if (sb->version == 0x00000200 && sb->platform == UBI_PC) { + sb->version = 0x00000000; + is_dino_pc = 1; + } + + /* Rayman 2: The Great Escape (1999)(PC)-bnm */ + /* Tonic Trouble (1999)(PC)-bnm */ + /* Donald Duck: Goin' Quackers (2000)(PC)-bnm */ + /* Disney's Dinosaur (2000)(PC)-bnm */ + if (sb->version == 0x00000000 && sb->platform == UBI_PC) { + config_sb_entry(sb, 0x20, 0x5c); + + config_sb_audio_fs(sb, 0x2c, 0x2c, 0x30); /* no group id */ + config_sb_audio_hs(sb, 0x42, 0x3c, 0x34, 0x34, 0x48, 0x44); + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x24, 0x18); + + config_sb_random_old(sb, 0x18, 0x0c); /* Rayman 2 needs it for rare sequence ends (ex. Bnk_31.bnm) */ + + /* no layers */ + + if (is_dino_pc) + config_sb_entry(sb, 0x20, 0x60); + return 1; + } + /* Batman: Vengeance (2001)(PC)-map */ - if (sb->version == 0x00000003 && sb->platform == UBI_PC) { + /* Batman: Vengeance (2001)(Xbox)-map */ + if ((sb->version == 0x00000003 && sb->platform == UBI_PC) || + (sb->version == 0x00000003 && sb->platform == UBI_XBOX)) { config_sb_entry(sb, 0x40, 0x68); config_sb_audio_fs(sb, 0x30, 0x30, 0x34); /* no group id? use external flag */ @@ -2055,6 +2395,22 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } + /* Batman: Rise of Sin Tzu (2003)(Xbox)-map 0x000A0003 */ + if (sb->version == 0x000A0003 && sb->platform == UBI_XBOX) { + config_sb_entry(sb, 0x64, 0x80); + + config_sb_audio_fs(sb, 0x24, 0x28, 0x2c); + config_sb_audio_hs(sb, 0x52, 0x4c, 0x38, 0x40, 0x58, 0x54); /* stream_type may contain garbage */ + sb->cfg.audio_has_internal_names = 1; + + config_sb_sequence(sb, 0x28, 0x14); + + config_sb_layer_hs(sb, 0x20, 0x60, 0x58, 0x30); + config_sb_layer_sh(sb, 0x14, 0x00, 0x06, 0x08, 0x10); + //todo some sequences mix 1ch and 2ch (voices?) + return 1; + } + /* Prince of Persia: The Sands of Time (2003)(Xbox)-bank 0x000A0004 / 0x000A0002 (POP1 port) */ if ((sb->version == 0x000A0002 && sb->platform == UBI_XBOX) || (sb->version == 0x000A0004 && sb->platform == UBI_XBOX)) { @@ -2342,8 +2698,23 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } + /* Tom Clancy's Ghost Recon Advanced Warfighter (2006)(X360)-bank */ + if (sb->version == 0x00170001 && sb->platform == UBI_X360) { + config_sb_entry(sb, 0x68, 0x70); + + config_sb_audio_fs(sb, 0x2c, 0x30, 0x34); + config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); + sb->cfg.audio_xma_offset = 0; /* header is in the extra table */ + + config_sb_sequence(sb, 0x2c, 0x14); + + config_sb_layer_he(sb, 0x20, 0x38, 0x40, 0x48); + config_sb_layer_sh(sb, 0x30, 0x00, 0x08, 0x0c, 0x14); + sb->cfg.layer_hijack = 1; /* WTF!!! layer format different from other layers using same id!!! */ + return 1; + } + /* Open Season (2006)(PC)-map 0x00180003 */ - /* Shaun White Snowboarding (2008)(PC)-map 0x00180003 */ if (sb->version == 0x00180003 && sb->platform == UBI_PC) { config_sb_entry(sb, 0x68, 0x78); @@ -2354,6 +2725,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); + + config_sb_silence_f(sb, 0x1c); return 1; } @@ -2368,6 +2741,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x2c, 0x30, 0x38); config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); + + config_sb_silence_f(sb, 0x1c); return 1; } @@ -2382,11 +2757,23 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); + + config_sb_silence_f(sb, 0x1c); return 1; } - /* Splinter Cell: Double Agent (2006)(PC)-map */ + + /* two configs with same id; use project file as identifier */ if (sb->version == 0x00180006 && sb->platform == UBI_PC) { + STREAMFILE * streamTest = open_streamfile_by_filename(streamFile, "Sc4_online_SoundProject.SP0"); + if (streamTest) { + is_sc4_pc_online = 1; + close_streamfile(streamTest); + } + } + + /* Splinter Cell: Double Agent (2006)(PC)-map (offline) */ + if (sb->version == 0x00180006 && sb->platform == UBI_PC && !is_sc4_pc_online) { config_sb_entry(sb, 0x68, 0x7c); config_sb_audio_fs(sb, 0x2c, 0x34, 0x30); @@ -2397,6 +2784,18 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { return 1; } + /* Splinter Cell: Double Agent (2006)(PC)-map (online) */ + if (sb->version == 0x00180006 && sb->platform == UBI_PC && is_sc4_pc_online) { + config_sb_entry(sb, 0x68, 0x78); + + config_sb_audio_fs(sb, 0x2c, 0x34, 0x30); + config_sb_audio_he(sb, 0x5c, 0x54, 0x40, 0x48, 0x64, 0x60); + + config_sb_layer_he(sb, 0x20, 0x38, 0x3c, 0x44); + config_sb_layer_sh(sb, 0x34, 0x00, 0x08, 0x0c, 0x14); + return 1; + } + /* Splinter Cell: Double Agent (2006)(X360)-map */ if (sb->version == 0x00180006 && sb->platform == UBI_X360) { config_sb_entry(sb, 0x68, 0x78); @@ -2483,6 +2882,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { (sb->version == 0x00190005 && sb->platform == UBI_PS3) || (sb->version == 0x00190005 && sb->platform == UBI_X360)) { config_sb_entry(sb, 0x68, 0x70); + sb->cfg.audio_fix_psx_samples = 1; /* ex. RSV PS3: 3n#10, SC DA PS3 */ config_sb_audio_fs(sb, 0x28, 0x2c, 0x30); config_sb_audio_he(sb, 0x3c, 0x40, 0x48, 0x50, 0x58, 0x5c); @@ -2494,8 +2894,6 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_layer_he(sb, 0x20, 0x34, 0x38, 0x40); config_sb_layer_sh(sb, 0x30, 0x00, 0x04, 0x08, 0x10); - //todo Splinter Cell: Double Agent (PS3) #13214 (PS-ADPCM + looping) has double num_samples, may need a flag - //AC1 PS3 also does it for PS-ADPCM + looping only (not AT3 or non-looping) */ return 1; } @@ -2527,6 +2925,7 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { config_sb_audio_he(sb, 0x44, 0x48, 0x50, 0x58, 0x60, 0x64); sb->cfg.audio_xma_offset = 0x78; sb->cfg.audio_interleave = 0x10; + sb->cfg.audio_fix_psx_samples = 1; config_sb_sequence(sb, 0x2c, 0x14); @@ -2547,6 +2946,8 @@ static int config_sb_version(ubi_sb_header * sb, STREAMFILE *streamFile) { /* Splinter Cell Classic Trilogy HD (2011)(PS3)-map */ if (sb->version == 0x001D0000 && sb->platform == UBI_PS3) { config_sb_entry(sb, 0x5c, 0x80); + sb->cfg.audio_interleave = 0x10; + sb->cfg.audio_fix_psx_samples = 1; config_sb_audio_fs(sb, 0x28, 0x30, 0x34); config_sb_audio_he(sb, 0x44, 0x4c, 0x54, 0x5c, 0x64, 0x68); diff --git a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb_streamfile.h index 45f03feaa..4d9f7e75d 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb_streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/meta/ubi_sb_streamfile.h @@ -11,6 +11,7 @@ typedef struct { int layer_count; int layer_max; int big_endian; + int layer_hijack; /* internal config */ off_t header_next_start; /* offset to header field */ @@ -148,6 +149,11 @@ static int ubi_sb_io_init(STREAMFILE *streamfile, ubi_sb_io_data* data) { /* Layers have a main header, then headered blocks with data. * We configure stuff to unify parsing of all variations. */ version = (uint32_t)read_32bit(offset+0x00, streamfile); + + /* it was bound to happen... orz */ + if (data->layer_hijack == 1 && version == 0x000B0008) + version = 0xFFFF0007; + switch(version) { case 0x00000002: /* Splinter Cell */ /* - layer header @@ -228,6 +234,35 @@ static int ubi_sb_io_init(STREAMFILE *streamfile, ubi_sb_io_data* data) { data->block_data_start = 0x0c + data->layer_max*0x04; break; + case 0xFFFF0007: /* Ghost Recon Advanced Warfighter (X360) */ + /* - layer header + * 0x04: config? + * 0x08: layer count + * 0x0c: stream size + * 0x10: block count + * 0x14: block header size + * 0x18: block size (fixed) + * 0x1c+(04*11): min layer data? for 11 layers (-1 after layer count) + * 0x48: size of header sizes + * 0x4c+(04*N): header size per layer + * 0xNN: header data per layer + * - block header + * 0x00: block number + * 0x04: block offset + * 0x08: always 0x03 + * 0x0c+(04*N): layer size per layer + * 0xNN: layer data per layer */ + data->layer_max = read_32bit(offset+0x08, streamfile); + + data->header_next_start = 0x18; + data->header_sizes_start = 0x4c; + data->header_data_start = 0x4c + data->layer_max*0x04; + + data->block_next_start = 0; + data->block_sizes_start = 0x0c; + data->block_data_start = 0x0c + data->layer_max*0x04; + break; + case 0x00040008: /* Assassin's Creed */ case 0x000B0008: /* Open Season, Surf's Up, TMNT, Splinter Cell HD */ case 0x000C0008: /* Splinter Cell: Double Agent */ @@ -326,7 +361,7 @@ fail: /* Handles deinterleaving of Ubisoft's headered+blocked 'multitrack' streams */ -static STREAMFILE* setup_ubi_sb_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, int layer_count, int big_endian) { +static STREAMFILE* setup_ubi_sb_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t stream_size, int layer_number, int layer_count, int big_endian, int layer_hijack) { STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; ubi_sb_io_data io_data = {0}; size_t io_data_size = sizeof(ubi_sb_io_data); @@ -336,6 +371,7 @@ static STREAMFILE* setup_ubi_sb_streamfile(STREAMFILE *streamFile, off_t stream_ io_data.layer_number = layer_number; io_data.layer_count = layer_count; io_data.big_endian = big_endian; + io_data.layer_hijack = layer_hijack; if (!ubi_sb_io_init(streamFile, &io_data)) goto fail; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/vag.c b/Frameworks/vgmstream/vgmstream/src/meta/vag.c index b4f2a9839..bc351c801 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/vag.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/vag.c @@ -90,7 +90,7 @@ VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile) { else if (read_32bitLE(0x1000,streamFile) == 0x56414770) /* "pGAV" */ interleave = 0x1000; /* Jak X interleave, includes header */ else - goto fail; + interleave = 0x2000; /* Jak 3 interleave in rare files, no header */ //todo interleave_first = interleave - start_offset; /* interleave includes header */ } else { @@ -203,6 +203,15 @@ VGMSTREAM * init_vgmstream_vag(STREAMFILE *streamFile) { channel_size = channel_size / channel_count; /* mono files also have channel/volume, but start at 0x30 and are probably named .vag */ } + else if (read_32bitBE(0x30,streamFile) == 0x53544552 /* "STER" */ + && read_32bitBE(0x34,streamFile) == 0x454F5641 /* "EOVA" */ + && read_32bitBE(0x38,streamFile) == 0x47324B00){ /* "G2K " */ + /* The Simpsons Skateboarding (PS2) */ + start_offset = 0x800; + channel_count = 2; + interleave = 0x800; + loop_flag = 0; + } else { /* standard PS1/PS2/PS3 .vag [Ecco the Dolphin (PS2), Legasista (PS3)] */ start_offset = 0x30; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/vpk.c b/Frameworks/vgmstream/vgmstream/src/meta/vpk.c index 1dd68bed8..7718b0b06 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/vpk.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/vpk.c @@ -5,7 +5,7 @@ VGMSTREAM * init_vgmstream_ps2_vpk(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; int loop_flag, channel_count; - off_t start_offset; + off_t start_offset, loop_channel_offset; size_t channel_size; @@ -16,13 +16,14 @@ VGMSTREAM * init_vgmstream_ps2_vpk(STREAMFILE *streamFile) { if (read_32bitBE(0x00,streamFile) != 0x204B5056) /* " KPV" */ goto fail; - /* seems this consistently has 0x10-0x20 extra bytes, landing in garbage frames at the end */ + /* seems this consistently has 0x10-0x20 extra bytes, landing in garbage 0xC00000..00 frames at the end */ channel_size = read_32bitLE(0x04,streamFile) - 0x20; /* remove for cleaner ends */ start_offset = read_32bitLE(0x08,streamFile); channel_count = read_32bitLE(0x14,streamFile); - /* 0x18+(per channel): channel config? */ - loop_flag = (read_32bitLE(0x7FC,streamFile) != 0); /* used? */ + /* 0x18+(per channel): channel config(?) */ + loop_channel_offset = read_32bitLE(0x7FC,streamFile); + loop_flag = (loop_channel_offset != 0); /* found in Sly 2/3 */ /* build the VGMSTREAM */ @@ -32,7 +33,7 @@ VGMSTREAM * init_vgmstream_ps2_vpk(STREAMFILE *streamFile) { vgmstream->sample_rate = read_32bitLE(0x10,streamFile); vgmstream->num_samples = ps_bytes_to_samples(channel_size*vgmstream->channels,vgmstream->channels); if (vgmstream->loop_flag) { - vgmstream->loop_start_sample = read_32bitLE(0x7FC,streamFile); + vgmstream->loop_start_sample = ps_bytes_to_samples(loop_channel_offset*vgmstream->channels,vgmstream->channels); vgmstream->loop_end_sample = vgmstream->num_samples; } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/vs_square.c b/Frameworks/vgmstream/vgmstream/src/meta/vs_square.c index a8e13baa8..79c42995d 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/vs_square.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/vs_square.c @@ -21,13 +21,13 @@ VGMSTREAM * init_vgmstream_vs_square(STREAMFILE *streamFile) { /* 0x08: block number */ /* 0x0c: blocks left in the subfile */ pitch = read_32bitLE(0x10,streamFile); /* usually 0x1000 = 48000 */ - /* 0x14: volume, usually 0x64 = 100 but be bigger/smaller (up to 128?) */ + /* 0x14: volume, usually 0x64 = 100 but may be bigger/smaller (up to 128?) */ /* 0x18: null */ /* 0x1c: null */ + /* some Front Mission 4 voices have flag 0x100, no idea */ if (flags != 0x00 && flags != 0x01) { - VGM_LOG("VS: unknown flags\n"); - goto fail; + VGM_LOG("VS: unknown flags %x\n", flags); } loop_flag = 0; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/wave_segmented.c b/Frameworks/vgmstream/vgmstream/src/meta/wave_segmented.c index 263b069b4..51fb0aeea 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/wave_segmented.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/wave_segmented.c @@ -204,6 +204,8 @@ VGMSTREAM * init_vgmstream_wave_segmented(STREAMFILE *streamFile) { vgmstream->loop_end_sample = loop_end_sample; vgmstream->meta_type = meta_WAVE_segmented; + vgmstream->stream_size = get_streamfile_size(streamFile); /* wrong kbps otherwise */ + /* .wave can mix codecs, usually first segment is a small ADPCM section) */ vgmstream->coding_type = (segment_count == 1 ? data->segments[0]->coding_type : data->segments[1]->coding_type); vgmstream->layout_type = layout_segmented; diff --git a/Frameworks/vgmstream/vgmstream/src/meta/wwise.c b/Frameworks/vgmstream/vgmstream/src/meta/wwise.c index 0a17ccd45..462de2a95 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/wwise.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/wwise.c @@ -29,6 +29,7 @@ typedef struct { int block_align; int average_bps; int bits_per_sample; + uint32_t channel_layout; size_t extra_size; int32_t num_samples; @@ -104,11 +105,12 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { if (ww.fmt_size < 0x12) goto fail; ww.format = (uint16_t)read_16bit(ww.fmt_offset+0x00,streamFile); - if (ww.format == 0x0165) { /* XMA2WAVEFORMAT (always "fmt"+"XMA2", unlike .xma that may only have "XMA2") */ + if (ww.format == 0x0165) { /* pseudo-XMA2WAVEFORMAT (always "fmt"+"XMA2", unlike .xma that may only have "XMA2") */ if (!find_chunk(streamFile, 0x584D4132,first_offset,0, &ww.chunk_offset,NULL, ww.big_endian, 0)) goto fail; xma2_parse_xma2_chunk(streamFile, ww.chunk_offset,&ww.channels,&ww.sample_rate, &ww.loop_flag, &ww.num_samples, &ww.loop_start_sample, &ww.loop_end_sample); - } else { /* WAVEFORMATEX */ + } + else { /* pseudo-WAVEFORMATEX */ ww.channels = read_16bit(ww.fmt_offset+0x02,streamFile); ww.sample_rate = read_32bit(ww.fmt_offset+0x04,streamFile); ww.average_bps = read_32bit(ww.fmt_offset+0x08,streamFile);/* bytes per sec */ @@ -116,9 +118,17 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { ww.bits_per_sample = (uint16_t)read_16bit(ww.fmt_offset+0x0e,streamFile); if (ww.fmt_size > 0x10 && ww.format != 0x0165 && ww.format != 0x0166) /* ignore XMAWAVEFORMAT */ ww.extra_size = (uint16_t)read_16bit(ww.fmt_offset+0x10,streamFile); - /* channel bitmask, see AkSpeakerConfig.h (ex. 1ch uses FRONT_CENTER 0x4, 2ch FRONT_LEFT 0x1 | FRONT_RIGHT 0x2, etc) */ - //if (ww.extra_size >= 6) - // ww.channel_config = read_32bit(ww.fmt_offset+0x14,streamFile); + if (ww.extra_size >= 0x06) { /* always present (actual RIFFs only have it in WAVEFORMATEXTENSIBLE) */ + /* mostly WAVEFORMATEXTENSIBLE's bitmask (see AkSpeakerConfig.h) */ + ww.channel_layout = read_32bit(ww.fmt_offset+0x14,streamFile); + /* latest games have a pseudo-format instead to handle more cases: + * - 8b: uNumChannels + * - 4b: eConfigType (0=none, 1=standard, 2=ambisonic) + * - 19b: uChannelMask */ + if ((ww.channel_layout & 0xFF) == ww.channels) { + ww.channel_layout = (ww.channel_layout >> 12); + } + } } /* find loop info */ @@ -192,6 +202,7 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { goto fail; } + start_offset = ww.data_offset; /* build the VGMSTREAM */ vgmstream = allocate_vgmstream(ww.channels,ww.loop_flag); @@ -200,10 +211,9 @@ VGMSTREAM * init_vgmstream_wwise(STREAMFILE *streamFile) { vgmstream->sample_rate = ww.sample_rate; vgmstream->loop_start_sample = ww.loop_start_sample; vgmstream->loop_end_sample = ww.loop_end_sample; + vgmstream->channel_layout = ww.channel_layout; vgmstream->meta_type = meta_WWISE_RIFF; - start_offset = ww.data_offset; - switch(ww.codec) { case PCM: /* common */ /* normally riff.c has priority but it's needed when .wem is used */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xnb.c b/Frameworks/vgmstream/vgmstream/src/meta/xnb.c index 2ca78366b..384a17f83 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/xnb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/xnb.c @@ -6,7 +6,7 @@ VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; off_t start_offset, xma_chunk_offset = 0; int loop_flag = 0, channel_count, num_samples = 0, loop_start = 0, loop_end = 0; - int big_endian, flags, codec, sample_rate, block_size, bps; + int big_endian, flags, codec, sample_rate, block_align, bps; size_t data_size; char platform; @@ -76,7 +76,7 @@ VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) { channel_count = read_16bit(current_offset+0x02, streamFile); sample_rate = read_32bit(current_offset+0x04, streamFile); /* 0x08: byte rate */ - block_size = read_16bit(current_offset+0x0c, streamFile); + block_align = read_16bit(current_offset+0x0c, streamFile); bps = read_16bit(current_offset+0x0e, streamFile); if (codec == 0x166) { @@ -104,29 +104,29 @@ VGMSTREAM * init_vgmstream_xnb(STREAMFILE *streamFile) { switch (codec) { case 0x01: /* Dragon's Blade (Android) */ /* null in Metagalactic Blitz (PC) */ - if (!block_size) - block_size = (bps == 8 ? 0x01 : 0x02) * channel_count; + if (!block_align) + block_align = (bps == 8 ? 0x01 : 0x02) * channel_count; vgmstream->coding_type = bps == 8 ? coding_PCM8_U_int : coding_PCM16LE; vgmstream->layout_type = layout_interleave; - vgmstream->interleave_block_size = block_size / channel_count; + vgmstream->interleave_block_size = block_align / channel_count; vgmstream->num_samples = pcm_bytes_to_samples(data_size, channel_count, bps); break; case 0x02: /* White Noise Online (PC) */ - if (!block_size) goto fail; + if (!block_align) goto fail; vgmstream->coding_type = coding_MSADPCM; vgmstream->layout_type = layout_none; - vgmstream->interleave_block_size = block_size; - vgmstream->num_samples = msadpcm_bytes_to_samples(data_size, block_size, channel_count); + vgmstream->interleave_block_size = block_align; + vgmstream->num_samples = msadpcm_bytes_to_samples(data_size, block_align, channel_count); break; case 0x11: - if (!block_size) goto fail; + if (!block_align) goto fail; vgmstream->coding_type = coding_MS_IMA; vgmstream->layout_type = layout_none; - vgmstream->interleave_block_size = block_size; - vgmstream->num_samples = ms_ima_bytes_to_samples(data_size, block_size, channel_count); + vgmstream->interleave_block_size = block_align; + vgmstream->num_samples = ms_ima_bytes_to_samples(data_size, block_align, channel_count); break; #ifdef VGM_USE_FFMPEG diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xvag.c b/Frameworks/vgmstream/vgmstream/src/meta/xvag.c index 2f0796062..1992619e5 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/xvag.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/xvag.c @@ -156,11 +156,8 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { case 0x08: { /* MPEG: The Last of Us (PS3), Uncharted 3 (PS3), Medieval Moves (PS3) */ mpeg_custom_config cfg = {0}; - if (xvag.subsongs > 1) goto fail; - if (xvag.subsongs > 1 && xvag.layers > 1) goto fail; /* often 2ch per MPEG and rarely 1ch (GoW3 PS4) */ if (xvag.layers > 1 && !(xvag.layers*1 == vgmstream->channels || xvag.layers*2 == vgmstream->channels)) goto fail; - //todo rare test file in The Last of Us PS4 uses 6ch with one 2ch stream, surround MPEG/mp3pro? (decoded samples map to 6ch) /* "mpin": mpeg info */ /* 0x00/04: mpeg version/layer? other: unknown or repeats of "fmat" */ @@ -173,6 +170,14 @@ VGMSTREAM * init_vgmstream_xvag(STREAMFILE *streamFile) { vgmstream->codec_data = init_mpeg_custom(streamFile, start_offset, &vgmstream->coding_type, vgmstream->channels, MPEG_XVAG, &cfg); if (!vgmstream->codec_data) goto fail; vgmstream->layout_type = layout_none; + + /* interleaved subsongs, rarely [Sly Cooper: Thieves in Time (PS3)] */ + if (xvag.subsongs > 1) { + temp_streamFile = setup_xvag_streamfile(streamFile, start_offset, cfg.interleave,cfg.chunk_size, (target_subsong-1), total_subsongs); + if (!temp_streamFile) goto fail; + start_offset = 0; + } + break; } #endif diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xwb.c b/Frameworks/vgmstream/vgmstream/src/meta/xwb.c index ebb74942e..744917a9d 100644 --- a/Frameworks/vgmstream/vgmstream/src/meta/xwb.c +++ b/Frameworks/vgmstream/vgmstream/src/meta/xwb.c @@ -1,7 +1,7 @@ #include "meta.h" -#include "../util.h" #include "../coding/coding.h" #include +#include "xwb_xsb.h" /* most info from XWBtool, xactwb.h, xact2wb.h and xact3wb.h */ @@ -68,6 +68,8 @@ typedef struct { uint32_t loop_start_sample; uint32_t loop_end_sample; + char wavebank_name[64+1]; + int is_crackdown; int fix_xma_num_samples; int fix_xma_loop_samples; @@ -79,7 +81,7 @@ static void get_name(char * buf, size_t maxsize, int target_subsong, xwb_header /* XWB - XACT Wave Bank (Microsoft SDK format for XBOX/XBOX360/Windows) */ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) { VGMSTREAM * vgmstream = NULL; - off_t start_offset, off, suboff; + off_t start_offset, offset, suboffset; xwb_header xwb = {0}; int target_subsong = streamFile->stream_index; int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; @@ -88,7 +90,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) { /* checks */ /* .xwb: standard * .xna: Touhou Makukasai ~ Fantasy Danmaku Festival (PC) - * (extensionless): Grabbed by the Ghoulies (Xbox) */ + * (extensionless): Ikaruga (X360/PC), Grabbed by the Ghoulies (Xbox) */ if (!check_extensions(streamFile,"xwb,xna,")) goto fail; if ((read_32bitBE(0x00,streamFile) != 0x57424E44) && /* "WBND" (LE) */ @@ -115,7 +117,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) { /* read segment offsets (SEGIDX) */ if (xwb.version <= XACT1_0_MAX) { xwb.total_subsongs = read_32bit(0x0c, streamFile); - /* 0x10: bank name (size 0x10) */ + read_string(xwb.wavebank_name,0x10+1, 0x10, streamFile); /* null-terminated */ xwb.base_offset = 0; xwb.base_size = 0; xwb.entry_offset = 0x50; @@ -131,68 +133,70 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) { xwb.extra_size = 0; } else { - off = xwb.version <= XACT2_2_MAX ? 0x08 : 0x0c; - xwb.base_offset = read_32bit(off+0x00, streamFile);//BANKDATA - xwb.base_size = read_32bit(off+0x04, streamFile); - xwb.entry_offset= read_32bit(off+0x08, streamFile);//ENTRYMETADATA - xwb.entry_size = read_32bit(off+0x0c, streamFile); + offset = xwb.version <= XACT2_2_MAX ? 0x08 : 0x0c; + xwb.base_offset = read_32bit(offset+0x00, streamFile);//BANKDATA + xwb.base_size = read_32bit(offset+0x04, streamFile); + xwb.entry_offset= read_32bit(offset+0x08, streamFile);//ENTRYMETADATA + xwb.entry_size = read_32bit(offset+0x0c, streamFile); /* read extra segments (values can be 0 == no segment) */ if (xwb.version <= XACT1_1_MAX) { - xwb.names_offset = read_32bit(off+0x10, streamFile);//ENTRYNAMES - xwb.names_size = read_32bit(off+0x14, streamFile); + xwb.names_offset = read_32bit(offset+0x10, streamFile);//ENTRYNAMES + xwb.names_size = read_32bit(offset+0x14, streamFile); xwb.names_entry_size= 0x40; xwb.extra_offset = 0; xwb.extra_size = 0; - suboff = 0x04*2; + suboffset = 0x04*2; } else if (xwb.version <= XACT2_1_MAX) { - xwb.names_offset = read_32bit(off+0x10, streamFile);//ENTRYNAMES - xwb.names_size = read_32bit(off+0x14, streamFile); + xwb.names_offset = read_32bit(offset+0x10, streamFile);//ENTRYNAMES + xwb.names_size = read_32bit(offset+0x14, streamFile); xwb.names_entry_size= 0x40; - xwb.extra_offset = read_32bit(off+0x18, streamFile);//EXTRA - xwb.extra_size = read_32bit(off+0x1c, streamFile); - suboff = 0x04*2 + 0x04*2; + xwb.extra_offset = read_32bit(offset+0x18, streamFile);//EXTRA + xwb.extra_size = read_32bit(offset+0x1c, streamFile); + suboffset = 0x04*2 + 0x04*2; } else { - xwb.extra_offset = read_32bit(off+0x10, streamFile);//SEEKTABLES - xwb.extra_size = read_32bit(off+0x14, streamFile); - xwb.names_offset = read_32bit(off+0x18, streamFile);//ENTRYNAMES - xwb.names_size = read_32bit(off+0x1c, streamFile); + xwb.extra_offset = read_32bit(offset+0x10, streamFile);//SEEKTABLES + xwb.extra_size = read_32bit(offset+0x14, streamFile); + xwb.names_offset = read_32bit(offset+0x18, streamFile);//ENTRYNAMES + xwb.names_size = read_32bit(offset+0x1c, streamFile); xwb.names_entry_size= 0x40; - suboff = 0x04*2 + 0x04*2; + suboffset = 0x04*2 + 0x04*2; } - xwb.data_offset = read_32bit(off+0x10+suboff, streamFile);//ENTRYWAVEDATA - xwb.data_size = read_32bit(off+0x14+suboff, streamFile); + xwb.data_offset = read_32bit(offset+0x10+suboffset, streamFile);//ENTRYWAVEDATA + xwb.data_size = read_32bit(offset+0x14+suboffset, streamFile); /* for Techland's XWB with no data */ if (xwb.base_offset == 0) goto fail; /* read base entry (WAVEBANKDATA) */ - off = xwb.base_offset; - xwb.base_flags = (uint32_t)read_32bit(off+0x00, streamFile); - xwb.total_subsongs = read_32bit(off+0x04, streamFile); - /* 0x08: bank name (size 0x40) */ - suboff = 0x08 + (xwb.version <= XACT1_1_MAX ? 0x10 : 0x40); - xwb.entry_elem_size = read_32bit(off+suboff+0x00, streamFile); + offset = xwb.base_offset; + xwb.base_flags = (uint32_t)read_32bit(offset+0x00, streamFile); + xwb.total_subsongs = read_32bit(offset+0x04, streamFile); + read_string(xwb.wavebank_name,0x40+1, offset+0x08, streamFile); /* null-terminated */ + suboffset = 0x08 + (xwb.version <= XACT1_1_MAX ? 0x10 : 0x40); + xwb.entry_elem_size = read_32bit(offset+suboffset+0x00, streamFile); /* suboff+0x04: meta name entry size */ - xwb.entry_alignment = read_32bit(off+suboff+0x08, streamFile); /* usually 1 dvd sector */ - xwb.format = read_32bit(off+suboff+0x0c, streamFile); /* compact mode only */ + xwb.entry_alignment = read_32bit(offset+suboffset+0x08, streamFile); /* usually 1 dvd sector */ + xwb.format = read_32bit(offset+suboffset+0x0c, streamFile); /* compact mode only */ /* suboff+0x10: build time 64b (XACT2/3) */ } + //;VGM_LOG("XWB: wavebank name='%s'\n", xwb.wavebank_name); + if (target_subsong == 0) target_subsong = 1; /* auto: default to 1 */ if (target_subsong < 0 || target_subsong > xwb.total_subsongs || xwb.total_subsongs < 1) goto fail; /* read stream entry (WAVEBANKENTRY) */ - off = xwb.entry_offset + (target_subsong-1) * xwb.entry_elem_size; + offset = xwb.entry_offset + (target_subsong-1) * xwb.entry_elem_size; if (xwb.base_flags & WAVEBANK_FLAGS_COMPACT) { /* compact entry [NFL Fever 2004 demo from Amped 2 (Xbox)] */ uint32_t entry, size_deviation, sector_offset; off_t next_stream_offset; - entry = (uint32_t)read_32bit(off+0x00, streamFile); + entry = (uint32_t)read_32bit(offset+0x00, streamFile); size_deviation = ((entry >> 21) & 0x7FF); /* 11b, padding data for sector alignment in bytes*/ sector_offset = (entry & 0x1FFFFF); /* 21b, offset within data in sectors */ @@ -200,7 +204,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) { /* find size using next offset */ if (target_subsong < xwb.total_subsongs) { - uint32_t next_entry = (uint32_t)read_32bit(off+0x04, streamFile); + uint32_t next_entry = (uint32_t)read_32bit(offset+0x04, streamFile); next_stream_offset = xwb.data_offset + (next_entry & 0x1FFFFF)*xwb.entry_alignment; } else { /* for last entry (or first, when subsongs = 1) */ @@ -209,31 +213,31 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) { xwb.stream_size = next_stream_offset - xwb.stream_offset - size_deviation; } else if (xwb.version <= XACT1_0_MAX) { - xwb.format = (uint32_t)read_32bit(off+0x00, streamFile); - xwb.stream_offset = xwb.data_offset + (uint32_t)read_32bit(off+0x04, streamFile); - xwb.stream_size = (uint32_t)read_32bit(off+0x08, streamFile); + xwb.format = (uint32_t)read_32bit(offset+0x00, streamFile); + xwb.stream_offset = xwb.data_offset + (uint32_t)read_32bit(offset+0x04, streamFile); + xwb.stream_size = (uint32_t)read_32bit(offset+0x08, streamFile); - xwb.loop_start = (uint32_t)read_32bit(off+0x0c, streamFile); - xwb.loop_end = (uint32_t)read_32bit(off+0x10, streamFile);//length + xwb.loop_start = (uint32_t)read_32bit(offset+0x0c, streamFile); + xwb.loop_end = (uint32_t)read_32bit(offset+0x10, streamFile);//length } else { - uint32_t entry_info = (uint32_t)read_32bit(off+0x00, streamFile); + uint32_t entry_info = (uint32_t)read_32bit(offset+0x00, streamFile); if (xwb.version <= XACT1_1_MAX) { xwb.entry_flags = entry_info; } else { xwb.entry_flags = (entry_info) & 0xF; /*4b*/ xwb.num_samples = (entry_info >> 4) & 0x0FFFFFFF; /*28b*/ } - xwb.format = (uint32_t)read_32bit(off+0x04, streamFile); - xwb.stream_offset = xwb.data_offset + (uint32_t)read_32bit(off+0x08, streamFile); - xwb.stream_size = (uint32_t)read_32bit(off+0x0c, streamFile); + xwb.format = (uint32_t)read_32bit(offset+0x04, streamFile); + xwb.stream_offset = xwb.data_offset + (uint32_t)read_32bit(offset+0x08, streamFile); + xwb.stream_size = (uint32_t)read_32bit(offset+0x0c, streamFile); if (xwb.version <= XACT2_1_MAX) { /* LoopRegion (bytes) */ - xwb.loop_start = (uint32_t)read_32bit(off+0x10, streamFile); - xwb.loop_end = (uint32_t)read_32bit(off+0x14, streamFile);//length (LoopRegion) or offset (XMALoopRegion in late XACT2) + xwb.loop_start = (uint32_t)read_32bit(offset+0x10, streamFile); + xwb.loop_end = (uint32_t)read_32bit(offset+0x14, streamFile);//length (LoopRegion) or offset (XMALoopRegion in late XACT2) } else { /* LoopRegion (samples) */ - xwb.loop_start_sample = (uint32_t)read_32bit(off+0x10, streamFile); - xwb.loop_end_sample = (uint32_t)read_32bit(off+0x14, streamFile) + xwb.loop_start_sample; + xwb.loop_start_sample = (uint32_t)read_32bit(offset+0x10, streamFile); + xwb.loop_end_sample = (uint32_t)read_32bit(offset+0x14, streamFile) + xwb.loop_start_sample; } } @@ -572,6 +576,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) { temp_vgmstream->num_streams = vgmstream->num_streams; temp_vgmstream->stream_size = vgmstream->stream_size; temp_vgmstream->meta_type = vgmstream->meta_type; + strcpy(temp_vgmstream->stream_name, vgmstream->stream_name); close_vgmstream(vgmstream); return temp_vgmstream; @@ -596,7 +601,6 @@ fail: /* ****************************************************************************** */ -/* try to get the stream name in the .xwb, though they are very rarely included */ static int get_xwb_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamFile) { size_t read; @@ -612,362 +616,45 @@ fail: return 0; } -/* ****************************************************************************** */ -/* XSB parsing from xwb_split (mostly untouched), could be improved */ - -#define XSB_XACT1_MAX 11 -#define XSB_XACT2_MAX 41 - -/** - * XWB contain stream info (channels, loop, data etc), often from multiple streams. - * XSBs contain info about how to play sounds (volume, pitch, name, etc) from XWBs (music or SFX). - * We only need to parse the XSB for the stream names. - */ -typedef struct { - int sound_count; -} xsb_wavebank; - -typedef struct { - int stream_index; /* stream id in the xwb (doesn't need to match xsb sound order) */ - int wavebank; /* xwb id, if the xsb has multiple wavebanks */ - off_t name_index; /* name order */ - off_t name_offset; /* global offset to the name string */ - off_t sound_offset; /* global offset to the xsb sound */ - off_t unk_index; /* some kind of number up to sound_count or 0xffff */ -} xsb_sound; - -typedef struct { - /* XSB header info */ - xsb_sound * xsb_sounds; /* array of sounds info from the xsb, simplified */ - xsb_wavebank * xsb_wavebanks; /* array of wavebank info from the xsb, simplified */ - - off_t xsb_sounds_offset; - size_t xsb_sounds_count; - - size_t xsb_simple_sounds_offset; /* sound cues */ - size_t xsb_simple_sounds_count; - size_t xsb_complex_sounds_offset; - size_t xsb_complex_sounds_count; - - size_t xsb_wavebanks_count; - off_t xsb_nameoffsets_offset; -} xsb_header; - - -/* try to find the stream name in a companion XSB file, a comically complex cue format. */ -static int get_xsb_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamXwb, char* filename) { - STREAMFILE *streamFile = NULL; - int i,j, start_sound, cfg__start_sound = 0, cfg__selected_wavebank = 0; - int xsb_version; - off_t off, suboff, name_offset = 0; - int32_t (*read_32bit)(off_t,STREAMFILE*) = NULL; - int16_t (*read_16bit)(off_t,STREAMFILE*) = NULL; +static int get_xsb_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamFile) { xsb_header xsb = {0}; - - if (filename) - streamFile = open_streamfile_by_filename(streamXwb, filename); - else - streamFile = open_streamfile_by_ext(streamXwb, "xsb"); - if (!streamFile) goto fail; - - /* check header */ - if ((read_32bitBE(0x00,streamFile) != 0x5344424B) && /* "SDBK" (LE) */ - (read_32bitBE(0x00,streamFile) != 0x4B424453)) /* "KBDS" (BE) */ + xsb.selected_stream = target_subsong - 1; + if (!parse_xsb(&xsb, streamFile, xwb->wavebank_name)) goto fail; - if (read_32bitBE(0x00,streamFile) == 0x5344424B) { /* SDBK */ - read_32bit = read_32bitLE; - read_16bit = read_16bitLE; - } else { - read_32bit = read_32bitBE; - read_16bit = read_16bitBE; - } - - - - /* read main header (SoundBankHeader) */ - xsb_version = read_16bit(0x04, streamFile); - if ((xwb->version <= XACT1_1_MAX && xsb_version > XSB_XACT1_MAX) || (xwb->version <= XACT2_2_MAX && xsb_version > XSB_XACT2_MAX)) { - VGM_LOG("XSB: xsb and xwb are from different XACT versions (xsb v%i vs xwb v%i)\n", xsb_version, xwb->version); + if ((xwb->version <= XACT1_1_MAX && xsb.version > XSB_XACT1_2_MAX) || + (xwb->version <= XACT2_2_MAX && xsb.version > XSB_XACT2_MAX)) { + VGM_LOG("XSB: mismatched XACT versions: xsb v%i vs xwb v%i\n", xsb.version, xwb->version); goto fail; } - - off = 0; - if (xsb_version <= XSB_XACT1_MAX) { - xsb.xsb_wavebanks_count = 1; //(uint8_t)read_8bit(0x22, streamFile); - xsb.xsb_sounds_count = (uint16_t)read_16bit(0x1e, streamFile);//@ 0x1a? 0x1c? - //xsb.xsb_names_size = 0; - //xsb.xsb_names_offset = 0; - xsb.xsb_nameoffsets_offset = 0; - xsb.xsb_sounds_offset = 0x38; - } else if (xsb_version <= XSB_XACT2_MAX) { - xsb.xsb_simple_sounds_count = (uint16_t)read_16bit(0x09, streamFile); - xsb.xsb_complex_sounds_count = (uint16_t)read_16bit(0x0B, streamFile); - xsb.xsb_wavebanks_count = (uint8_t)read_8bit(0x11, streamFile); - xsb.xsb_sounds_count = (uint16_t)read_16bit(0x12, streamFile); - //0x14: 16b unk - //xsb.xsb_names_size = read_32bit(0x16, streamFile); - xsb.xsb_simple_sounds_offset = read_32bit(0x1a, streamFile); - xsb.xsb_complex_sounds_offset = read_32bit(0x1e, streamFile); //todo 0x1e? - //xsb.xsb_names_offset = read_32bit(0x22, streamFile); - xsb.xsb_nameoffsets_offset = read_32bit(0x3a, streamFile); - xsb.xsb_sounds_offset = read_32bit(0x3e, streamFile); - } else { - xsb.xsb_simple_sounds_count = (uint16_t)read_16bit(0x13, streamFile); - xsb.xsb_complex_sounds_count = (uint16_t)read_16bit(0x15, streamFile); - xsb.xsb_wavebanks_count = (uint8_t)read_8bit(0x1b, streamFile); - xsb.xsb_sounds_count = read_16bit(0x1c, streamFile); - //xsb.xsb_names_size = read_32bit(0x1e, streamFile); - xsb.xsb_simple_sounds_offset = read_32bit(0x22, streamFile); - xsb.xsb_complex_sounds_offset = read_32bit(0x26, streamFile); - //xsb.xsb_names_offset = read_32bit(0x2a, streamFile); - xsb.xsb_nameoffsets_offset = read_32bit(0x42, streamFile); - xsb.xsb_sounds_offset = read_32bit(0x46, streamFile); - } - - VGM_ASSERT(xsb.xsb_sounds_count < xwb->total_subsongs, - "XSB: number of streams in xsb lower than xwb (xsb %i vs xwb %i)\n", xsb.xsb_sounds_count, xwb->total_subsongs); - - VGM_ASSERT(xsb.xsb_simple_sounds_count + xsb.xsb_complex_sounds_count != xsb.xsb_sounds_count, - "XSB: number of xsb sounds doesn't match simple + complex sounds (simple %i, complex %i, total %i)\n", xsb.xsb_simple_sounds_count, xsb.xsb_complex_sounds_count, xsb.xsb_sounds_count); - - - /* init stuff */ - xsb.xsb_sounds = calloc(xsb.xsb_sounds_count, sizeof(xsb_sound)); - if (!xsb.xsb_sounds) goto fail; - - xsb.xsb_wavebanks = calloc(xsb.xsb_wavebanks_count, sizeof(xsb_wavebank)); - if (!xsb.xsb_wavebanks) goto fail; - - /* The following is a bizarre soup of flags, tables, offsets to offsets and stuff, just to get the actual name. - * info: https://wiki.multimedia.cx/index.php/XACT */ - - /* parse xsb sounds */ - off = xsb.xsb_sounds_offset; - for (i = 0; i < xsb.xsb_sounds_count; i++) { - xsb_sound *s = &(xsb.xsb_sounds[i]); - uint32_t flag; - size_t size; - - if (xsb_version <= XSB_XACT1_MAX) { - /* The format seems constant */ - flag = read_8bit(off+0x00, streamFile); - size = 0x14; - - if (flag != 0x01) { - //VGM_LOG("XSB: xsb flag 0x%x at offset 0x%08lx not implemented\n", flag, off); - goto fail; - } - - s->wavebank = 0; //(uint8_t)read_8bit(off+suboff + 0x02, streamFile); - s->stream_index = (uint16_t)read_16bit(off+0x02, streamFile); - s->sound_offset = off; - s->name_offset = (uint16_t)read_16bit(off+0x04, streamFile); - } - else { - /* Each XSB sound has a variable size and somewhere inside is the stream/wavebank index. - * Various flags control the sound layout, but I can't make sense of them so quick hack instead */ - flag = read_8bit(off+0x00, streamFile); - //0x01 16b unk, 0x03: 8b unk 04: 16b unk, 06: 8b unk - size = (uint16_t)read_16bit(off+0x07, streamFile); - - if (!(flag & 0x01)) { /* simple sound */ - suboff = 0x09; - } else { /* complex sound */ - /* not very exact but seems to work */ - if (flag==0x01 || flag==0x03 || flag==0x05 || flag==0x07) { - if (size == 0x49) { //grotesque hack for Eschatos (these flags are way too complex) - suboff = 0x23; - } else if (size % 2 == 1 && (uint16_t)read_16bit(off+size-0x2, streamFile)!=0) { - suboff = size - 0x08 - 0x07; //7 unk bytes at the end - } else { - suboff = size - 0x08; - } - //} else if (flag==0x11) { /* Stardew Valley (Switch) */ - // suboff = size; //??? - } else { - //VGM_LOG("XSB: xsb flag 0x%x (size=%x) at offset 0x%08lx not implemented\n", flag, size, off); - goto fail; - } - } - - s->stream_index = (uint16_t)read_16bit(off+suboff + 0x00, streamFile); - s->wavebank = (uint8_t)read_8bit(off+suboff + 0x02, streamFile); - s->sound_offset = off; - } - - if (s->wavebank+1 > xsb.xsb_wavebanks_count) { - //VGM_LOG("XSB: unknown xsb wavebank id %i at offset 0x%lx\n", s->wavebank, off); - goto fail; - } - - xsb.xsb_wavebanks[s->wavebank].sound_count += 1; - off += size; - } - - - /* parse name offsets */ - if (xsb_version > XSB_XACT1_MAX) { - /* "cue" name order: first simple sounds, then complex sounds - * Both aren't ordered like the sound entries, instead use a global offset to the entry - * - * ex. of a possible XSB: - * name 1 = simple sound 1 > sound entry 2 (points to xwb stream 4): stream 4 uses name 1 - * name 2 = simple sound 2 > sound entry 1 (points to xwb stream 1): stream 1 uses name 2 - * name 3 = complex sound 1 > sound entry 3 (points to xwb stream 3): stream 3 uses name 3 - * name 4 = complex sound 2 > sound entry 4 (points to xwb stream 2): stream 2 uses name 4 - * - * Multiple cues can point to the same sound entry but we only use the first name (meaning some won't be used) */ - off_t n_off = xsb.xsb_nameoffsets_offset; - - off = xsb.xsb_simple_sounds_offset; - for (i = 0; i < xsb.xsb_simple_sounds_count; i++) { - off_t sound_offset = read_32bit(off + 0x01, streamFile); - off += 0x05; - - /* find sound by offset */ - for (j = 0; j < xsb.xsb_sounds_count; j++) { - xsb_sound *s = &(xsb.xsb_sounds[j]);; - /* update with the current name offset */ - if (!s->name_offset && sound_offset == s->sound_offset) { - s->name_offset = read_32bit(n_off + 0x00, streamFile); - s->unk_index = read_16bit(n_off + 0x04, streamFile); - n_off += 0x06; - break; - } - } - } - - off = xsb.xsb_complex_sounds_offset; - for (i = 0; i < xsb.xsb_complex_sounds_count; i++) { - off_t sound_offset = read_32bit(off + 0x01, streamFile); - off += 0x0f; - - /* find sound by offset */ - for (j = 0; j < xsb.xsb_sounds_count; j++) { - xsb_sound *s = &(xsb.xsb_sounds[j]);; - /* update with the current name offset */ - if (!s->name_offset && sound_offset == s->sound_offset) { - s->name_offset = read_32bit(n_off + 0x00, streamFile); - s->unk_index = read_16bit(n_off + 0x04, streamFile); - n_off += 0x06; - break; - } - } - } - } - - // todo: it's possible to find the wavebank using the name - /* try to find correct wavebank, in cases of multiple */ - if (!cfg__selected_wavebank) { - for (i = 0; i < xsb.xsb_wavebanks_count; i++) { - xsb_wavebank *w = &(xsb.xsb_wavebanks[i]); - - //CHECK_EXIT(w->sound_count == 0, "ERROR: xsb wavebank %i has no sounds", i); //Ikaruga PC - - if (w->sound_count == xwb->total_subsongs) { - if (!cfg__selected_wavebank) { - //VGM_LOG("XSB: multiple xsb wavebanks with the same number of sounds, use -w to specify one of the wavebanks\n"); - goto fail; - } - - cfg__selected_wavebank = i+1; - } - } - } - - /* banks with different number of sounds but only one wavebank, just select the first */ - if (!cfg__selected_wavebank && xsb.xsb_wavebanks_count==1) { - cfg__selected_wavebank = 1; - } - - if (!cfg__selected_wavebank) { - //VGM_LOG("XSB: multiple xsb wavebanks but autodetect didn't work\n"); + //;VGM_LOG("XSB: name found=%i at %lx\n", xsb.parse_found, xsb.name_offset); + if (!xsb.parse_found || xsb.name_offset == 0) goto fail; - } - if (xsb.xsb_wavebanks[cfg__selected_wavebank-1].sound_count == 0) { - //VGM_LOG("XSB: xsb selected wavebank %i has no sounds\n", cfg__selected_wavebank); - goto fail; - } - - if (cfg__start_sound) { - if (xsb.xsb_wavebanks[cfg__selected_wavebank-1].sound_count - (cfg__start_sound-1) < xwb->total_subsongs) { - //VGM_LOG("XSB: starting sound too high (max in selected wavebank is %i)\n", xsb.xsb_wavebanks[cfg__selected_wavebank-1].sound_count - xwb->total_subsongs + 1); - goto fail; - } - - } else { - /* - if (!cfg->ignore_names_not_found) - CHECK_EXIT(xwb->xsb_wavebanks[cfg->selected_wavebank-1].sound_count > xwb->streams_count, "ERROR: number of streams in xsb wavebank bigger than xwb (xsb %i vs xwb %i), use -s to specify (1=first)", xwb->xsb_wavebanks[cfg->selected_wavebank-1].sound_count, xwb->total_subsongs); - if (!cfg->ignore_names_not_found) - CHECK_EXIT(xwb->xsb_wavebanks[cfg->selected_wavebank-1].sound_count < xwb->streams_count, "ERROR: number of streams in xsb wavebank lower than xwb (xsb %i vs xwb %i), use -n to ignore (some names won't be extracted)", xwb->xsb_wavebanks[cfg->selected_wavebank-1].sound_count, xwb->total_subsongs); - */ - - - //if (!cfg->ignore_names_not_found) - // CHECK_EXIT(xwb->xsb_wavebanks[cfg->selected_wavebank-1].sound_count != xwb->streams_count, "ERROR: number of streams in xsb wavebank different than xwb (xsb %i vs xwb %i), use -s to specify (1=first)", xwb->xsb_wavebanks[cfg->selected_wavebank-1].sound_count, xwb->total_subsongs); - } - - /* *************************** */ - - start_sound = cfg__start_sound ? cfg__start_sound-1 : 0; - - /* get name offset */ - for (i = start_sound; i < xsb.xsb_sounds_count; i++) { - xsb_sound *s = &(xsb.xsb_sounds[i]); - if (s->wavebank == cfg__selected_wavebank-1 - && s->stream_index == target_subsong-1){ - name_offset = s->name_offset; - break; - } - } - - if (name_offset) - read_string(buf,maxsize, name_offset,streamFile); - - //return; /* no return, let free */ + read_string(buf,maxsize, xsb.name_offset,streamFile); /* null-terminated */ + return 1; fail: - free(xsb.xsb_sounds); - free(xsb.xsb_wavebanks); - close_streamfile(streamFile); - - return (name_offset); + return 0; } -static void get_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamFile) { +static void get_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamXwb) { + STREAMFILE *streamXsb = NULL; int name_found; - char xwb_filename[PATH_LIMIT]; - char xsb_filename[PATH_LIMIT]; - /* try inside this xwb */ - name_found = get_xwb_name(buf, maxsize, target_subsong, xwb, streamFile); + /* try to get the stream name in the .xwb, though they are very rarely included */ + name_found = get_xwb_name(buf, maxsize, target_subsong, xwb, streamXwb); if (name_found) return; + /* try again in a companion .xsb file, a comically complex cue format */ + streamXsb = open_xsb_filename_pair(streamXwb); + if (!streamXsb) return; /* not all xwb have xsb though */ - /* try again in external .xsb, using a bunch of possible name pairs */ - get_streamfile_filename(streamFile,xwb_filename,PATH_LIMIT); + name_found = get_xsb_name(buf, maxsize, target_subsong, xwb, streamXsb); + close_streamfile(streamXsb); - if (strcmp(xwb_filename,"Wave Bank.xwb")==0) { - strcpy(xsb_filename,"Sound Bank.xsb"); + if (!name_found) { + buf[0] = '\0'; } - else if (strcmp(xwb_filename,"UIMusicBank.xwb")==0) { - strcpy(xsb_filename,"UISoundBank.xsb"); - } - else { - xsb_filename[0] = '\0'; - } - //todo try others: InGameMusic.xwb + ingamemusic.xsb, NB_BGM_m0100_WB.xwb + NB_BGM_m0100_SB.xsb, etc - - if (xsb_filename[0] != '\0') { - name_found = get_xsb_name(buf, maxsize, target_subsong, xwb, streamFile, xsb_filename); - if (name_found) return; - } - - - /* one last time with same name */ - get_xsb_name(buf, maxsize, target_subsong, xwb, streamFile, NULL); } diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h b/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h new file mode 100644 index 000000000..f42b24dc2 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/xwb_xsb.h @@ -0,0 +1,836 @@ +#ifndef _XWB_XSB_H_ +#define _XWB_XSB_H_ + +#define XSB_XACT1_0_MAX 5 /* Unreal Championship (Xbox) */ +#define XSB_XACT1_1_MAX 8 /* Die Hard: Vendetta (Xbox) */ +#define XSB_XACT1_2_MAX 11 /* other Xbox games */ +#define XSB_XACT2_MAX 41 /* other PC/X360 games */ + + +typedef struct { + /* config */ + int selected_stream; + int selected_wavebank; + + /* state */ + int big_endian; + int version; + + int simple_cues_count; + off_t simple_cues_offset; + int complex_cues_count; + off_t complex_cues_offset; + int sounds_count; + off_t sounds_offset; + int wavebanks_count; + off_t wavebanks_offset; + int wavebanks_name_size; + off_t nameoffsets_offset; + int cue_names_size; + off_t cue_names_offset; + + /* output */ + int parse_found; + int parse_done; + off_t name_offset; + +} xsb_header; + + +static void xsb_check_stream(xsb_header * xsb, int stream_index, int wavebank_index, off_t name_offset, STREAMFILE *sf) { + if (xsb->parse_done) + return; + + /* multiple names may correspond to a stream, so commenting parse_done + * will allow to search for other names instead of first only */ + if (xsb->selected_stream == stream_index && + (xsb->selected_wavebank == wavebank_index || wavebank_index == -1 || wavebank_index == 255)) { + xsb->name_offset = name_offset; + xsb->parse_found = 1; + xsb->parse_done = 1; + //;VGM_LOG("XSB: parse found stream=%i, wavebank=%i, name_offset=%lx\n", stream_index, wavebank_index, name_offset); + } + +#if 0 // for debugging purposes + { + char stream_name[255]; + read_string(stream_name,255, name_offset,sf); /* null-terminated */ + ;VGM_LOG("XSB: stream=%i, wavebank=%i, name=%lx=%s vs s=%i, w=%i\n", stream_index, wavebank_index, name_offset, stream_name, xsb->selected_stream, xsb->selected_wavebank); + xsb->parse_done = 0; /* keep parsing */ + } +#endif +} + +/* old XACT1 is a bit different and much of it is unknown but this seems to work. + * - after header is the simple(?) cues table then complex(?) cues table + * - simple cues point to complex cues by index + * - complex cues may have stream/wavebank or point again to a sound(?) with the stream/wavebank + */ +static int parse_xsb_cues_old(xsb_header * xsb, STREAMFILE *sf) { + int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; + int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; + + uint8_t flags, subflags; + int cue_index, stream_index, wavebank_index = 0; + off_t offset, name_offset, cue_offset, sound_offset; + int i; + size_t simple_entry, complex_entry; + + + if (xsb->version <= XSB_XACT1_1_MAX) { + simple_entry = 0x10; + complex_entry = 0x14; + } + else if (xsb->version <= XSB_XACT1_2_MAX) { + simple_entry = 0x14; + complex_entry = 0x14; + } + else { + VGM_LOG("XSB: unknown old format for version %x\n", xsb->version); + goto fail; + } + + + offset = xsb->sounds_offset; + for (i = 0; i < xsb->simple_cues_count; i++) { + + /* *** simple sound *** */ + /* 00(2): flags? */ + cue_index = read_s16(offset + 0x02,sf); + name_offset = read_s32(offset + 0x04,sf); + /* 06-14: unknown */ + + //;VGM_LOG("XSB old simple at %lx: cue=%i, name_offset=%lx\n", offset, cue_index, name_offset); + offset += simple_entry; + + /* when cue_index is -1 @0x08 points to some offset (random sound type?) [ex. ATV 3 Lawless (Xbox)] */ + if (cue_index < 0 && cue_index > xsb->complex_cues_count) { + VGM_LOG("XSB old: ignored cue index %i\n", cue_index); + continue; + } + + + /* *** complex sound *** */ + cue_offset = xsb->sounds_offset + xsb->simple_cues_count*simple_entry + cue_index*complex_entry; + + /* most fields looks like more flags and optional offsets depending of flags */ + flags = read_u8(cue_offset + 0x0b,sf); + + if (flags & 8) { /* simple */ + stream_index = read_s16(cue_offset + 0x00,sf); + wavebank_index = read_s16(cue_offset + 0x02,sf); + } + //else if (flags & 4) { /* unsure */ + // VGM_LOG("XSB old complex at %lx: unknown flags=%x\n", cue_offset, flags); + // continue; + //} + else { /* complex (flags none/1/2) */ + sound_offset = read_s32(cue_offset + 0x00,sf); + + /* *** jump entry *** */ + /* 00(1): flags? */ + sound_offset = read_s32(sound_offset + 0x01,sf) & 0x00FFFFFF; /* 24b */ + + /* *** sound entry *** */ + subflags = read_u8(sound_offset + 0x00,sf); + if (subflags == 0x00) { /* 0x0c entry */ + stream_index = read_s16(sound_offset + 0x08,sf); + wavebank_index = read_s16(sound_offset + 0x0a,sf); + } + else if (subflags == 0x0a) { /* 0x20 entry */ + stream_index = read_s16(sound_offset + 0x1c,sf); + wavebank_index = read_s16(sound_offset + 0x1e,sf); + } + else { + VGM_LOG("XSB old sound at %lx: unknown subflags=%x\n", sound_offset, subflags); + continue; + } + } + + //;VGM_LOG("XSB old complex at %lx: flags=%x, stream=%i, wavebank=%i, name_offset=%lx\n", cue_offset, flags, stream_index, wavebank_index, name_offset); + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf); + if (xsb->parse_done) return 1; + } + + return 1; +fail: + return 0; +} + +static int parse_xsb_clip(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) { + uint32_t (*read_u32)(off_t,STREAMFILE*) = xsb->big_endian ? read_u32be : read_u32le; + int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; + + uint32_t flags; + int stream_index, wavebank_index; + int i, t, track_count, event_count; + + + event_count = read_s8(offset + 0x00,sf); + + //;VGM_LOG("XSB clip at %lx\n", offset); + offset += 0x01; + + for (i = 0; i < event_count; i++) { + flags = read_u32(offset + 0x00,sf); + /* 04(2): random offset */ + + //;VGM_LOG("XSB clip event: %x at %lx\n", flags, offset); + offset += 0x06; + + switch (flags & 0x1F) { /* event ID */ + + case 0x01: /* playwave event */ + /* 00(1): unknown */ + /* 01(1): flags */ + stream_index = read_s16(offset + 0x02,sf); + wavebank_index = read_s8 (offset + 0x04,sf); + /* 05(1): loop count */ + /* 06(2): pan angle */ + /* 08(2): pan arc */ + + //;VGM_LOG("XSB clip event 1 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index); + offset += 0x0a; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf); + if (xsb->parse_done) return 1; + break; + + case 0x03: /* playwave event */ + /* 00(1): unknown */ + /* 01(1): flags */ + /* 02(1): loop count */ + /* 03(2): pan angle */ + /* 05(2): pan arc */ + track_count = read_s16(offset + 0x07,sf); + /* 09(1): flags? */ + /* 0a(5): unknown */ + + //;VGM_LOG("XSB clip event 3 at %lx\n", offset); + offset += 0x0F; + + for (t = 0; t < track_count; t++) { + stream_index = read_s16(offset + 0x00,sf); + wavebank_index = read_s8 (offset + 0x02,sf); + /* 03(1): min weight */ + /* 04(1): min weight */ + + //;VGM_LOG("XSB clip event 3: track=%i, stream=%i, wavebank=%i\n", t, stream_index, wavebank_index); + offset += 0x05; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf); + if (xsb->parse_done) return 1; + } + break; + + case 0x04: /* playwave event */ + /* 00(1): unknown */ + /* 01(1): flags */ + stream_index = read_s16(offset + 0x02,sf); + wavebank_index = read_s8 (offset + 0x04,sf); + /* 05(1): loop count */ + /* 06(2): pan angle */ + /* 08(2): pan arc */ + /* 0a(2): min pitch */ + /* 0c(2): max pitch */ + /* 0e(1): min volume */ + /* 0f(1): max volume */ + /* 10(4): min frequency */ + /* 14(4): max frequency */ + /* 18(1): min Q */ + /* 19(1): max Q */ + /* 1a(1): unknown */ + /* 1b(1): variation flags */ + + //;VGM_LOG("XSB clip event 4 at %lx: stream=%i, wavebank=%i\n", offset, stream_index, wavebank_index); + offset += 0x1c; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf); + if (xsb->parse_done) return 1; + break; + + case 0x06: /* playwave event */ + /* 00(1): unknown */ + /* 01(1): flags */ + /* 02(1): loop count */ + /* 03(2): pan angle */ + /* 05(2): pan arc */ + /* 07(2): min pitch */ + /* 09(2): max pitch */ + /* 0b(1): min volume */ + /* 0c(1): max volume */ + /* 0d(4): min frequency */ + /* 11(4): max frequency */ + /* 15(1): min Q */ + /* 16(1): max Q */ + /* 17(1): unknown */ + /* 18(1): variation flags */ + track_count = read_s16(offset + 0x19,sf); + /* 1a(1): flags 2 */ + /* 1b(5): unknown 2 */ + + //;VGM_LOG("XSB clip event 6 at %lx\n", offset); + offset += 0x20; + + for (t = 0; t < track_count; t++) { + stream_index = read_s16(offset + 0x00,sf); + wavebank_index = read_s8 (offset + 0x02,sf); + /* 03(1): min weight */ + /* 04(1): min weight */ + + //;VGM_LOG("XSB clip event 6: track=%i, stream=%i, wavebank=%i at %lx\n", t, stream_index, wavebank_index, offset); + offset += 0x05; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf); + if (xsb->parse_done) return 1; + } + break; + + case 0x08: /* volume event */ + /* 00(2): unknown */ + /* 02(1): flags */ + /* 03(4): decibels */ + /* 07(9): unknown */ + + //;VGM_LOG("XSB clip event 8 at %lx\n", offset); + offset += 0x10; + break; + + case 0x00: /* stop event */ + case 0x07: /* pitch event */ + case 0x09: /* marker event */ + case 0x11: /* volume repeat event */ + default: + VGM_LOG("XSB event: unknown type %x at %lx\n", flags, offset); + goto fail; + } + } + + return 1; +fail: + return 0; +} + +static int parse_xsb_sound(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) { + int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; + int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; + + uint8_t flags; + int stream_index = 0, wavebank_index = 0; + int i, clip_count = 0; + + + flags = read_u8 (offset + 0x00,sf); + /* 0x01(2): category */ + /* 0x03(1): decibels */ + /* 0x04(2): pitch */ + /* 0x06(1): priority */ + /* 0x07(2): entry size? "filter stuff"? */ + + //;VGM_LOG("XSB sound at %lx\n", offset); + offset += 0x09; + + if (flags & 0x01) { /* complex sound */ + clip_count = read_u8 (offset + 0x00,sf); + + //;VGM_LOG("XSB sound: complex with clips=%i\n", clip_count); + offset += 0x01; + } + else { + stream_index = read_s16(offset + 0x00,sf); + wavebank_index = read_s8(offset + 0x02,sf); + + //;VGM_LOG("XSB sound: simple with stream=%i, wavebank=%i\n", stream_index, wavebank_index); + offset += 0x03; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf); + if (xsb->parse_done) return 1; + } + + if (flags & 0x0E) { /* has RPCs */ + size_t rpc_size = read_s16(offset + 0x00,sf); + /* 0x02(2): preset count */ + /* 0x04(4*count): RPC indexes */ + /* (presets per flag 2/4/8 flag) */ + offset += rpc_size; + } + + if (flags & 0x10) { /* has DSPs */ + size_t dsp_size = read_s16(offset + 0x00,sf); + /* follows RPC format? */ + offset += dsp_size; + } + + if (flags & 0x01) { /* complex sound clips */ + off_t clip_offset; + for (i = 0; i < clip_count; i++) { + /* 00(1): decibels */ + clip_offset = read_s32(offset + 0x01,sf); + /* 05(2): filter config */ + /* 07(2): filter frequency */ + + //;VGM_LOG("XSB sound clip %i at %lx\n", i, offset); + offset += 0x09; + + parse_xsb_clip(xsb, clip_offset, name_offset,sf); + if (xsb->parse_done) return 1; + } + } + + return 0; +} + +static int parse_xsb_variation(xsb_header * xsb, off_t offset, off_t name_offset, STREAMFILE *sf) { + int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; + uint16_t (*read_u16)(off_t,STREAMFILE*) = xsb->big_endian ? read_u16be : read_u16le; + int16_t (*read_s16)(off_t,STREAMFILE*) = xsb->big_endian ? read_s16be : read_s16le; + + uint16_t flags; + int stream_index, wavebank_index; + int i, variation_count; + + + variation_count = read_s16(offset + 0x00,sf); + flags = read_u16(offset + 0x02,sf); + + //;VGM_LOG("XSB variation at %lx\n", offset); + offset += 0x04; + + for (i = 0; i < variation_count; i++) { + off_t sound_offset; + + switch ((flags >> 3) & 0x7) { + case 0: /* wave */ + stream_index = read_s16(offset + 0x00,sf); + wavebank_index = read_s8(offset + 0x02,sf); + /* 03(1): weight min */ + /* 04(1): weight max */ + + //;VGM_LOG("XSB variation: type 0 with stream=%i, wavebank=%i\n", stream_index, wavebank_index); + offset += 0x05; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf); + if (xsb->parse_done) return 1; + break; + + case 1: /* sound */ + sound_offset = read_s32(offset + 0x00,sf); + /* 04(1): weight min */ + /* 05(1): weight max */ + + //;VGM_LOG("XSB variation: type 1\n"); + offset += 0x06; + + parse_xsb_sound(xsb, sound_offset, name_offset,sf); + if (xsb->parse_done) return 1; + break; + + case 3: /* sound */ + sound_offset = read_s32(offset + 0x00,sf); + /* 04(4): weight min */ + /* 08(4): weight max */ + /* 0c(4): flags */ + + //;VGM_LOG("XSB variation: type 3\n"); + offset += 0x10; + + parse_xsb_sound(xsb, sound_offset, name_offset,sf); + if (xsb->parse_done) return 1; + break; + + case 4: /* compact wave */ + stream_index = read_s16(offset + 0x00,sf); + wavebank_index = read_s8(offset + 0x02,sf); + + //;VGM_LOG("XSB variation: type 4 with stream=%i, wavebank=%i\n", stream_index, wavebank_index); + offset += 0x03; + + xsb_check_stream(xsb, stream_index, wavebank_index, name_offset,sf); + if (xsb->parse_done) return 1; + break; + + default: + VGM_LOG("XSB variation: unknown type %x at %lx\n", flags, offset); + goto fail; + } + } + + /* 00(1): unknown */ + /* 01(2): unknown */ + /* 03(1): unknown */ + offset += 0x04; + + + return 1; +fail: + return 0; +} + + +static int parse_xsb_cues_new(xsb_header * xsb, STREAMFILE *sf) { + int32_t (*read_s32)(off_t,STREAMFILE*) = xsb->big_endian ? read_s32be : read_s32le; + + uint8_t flags; + off_t offset, name_offset, sound_offset; + off_t names_offset = xsb->nameoffsets_offset; + int i; + + + offset = xsb->simple_cues_offset; + for (i = 0; i < xsb->simple_cues_count; i++) { + /* 00(1): flags */ + sound_offset = read_s32(offset + 0x01,sf); + + //;VGM_LOG("XSB cues: simple %i at %lx\n", i, offset); + offset += 0x05; + + name_offset = read_s32(names_offset + 0x00,sf); + /* 04(2): unknown (-1) */ + names_offset += 0x06; + + parse_xsb_sound(xsb, sound_offset, name_offset,sf); + if (xsb->parse_done) break; + } + + offset = xsb->complex_cues_offset; + for (i = 0; i < xsb->complex_cues_count; i++) { + flags = read_u8(offset + 0x00,sf); + sound_offset = read_s32(offset + 0x01,sf); + /* 05(4): unknown (sound) / transition table offset (variation) */ + /* 09(1): instance limit */ + /* 0a(2): fade in sec */ + /* 0c(2): fade out sec */ + /* 0e(1): instance flags */ + + //;VGM_LOG("XSB cues: complex %i at %lx\n", i, offset); + offset += 0x0f; + + name_offset = read_s32(names_offset + 0x00,sf); + /* 04(2): unknown (-1) */ + names_offset += 0x06; + + if (flags & (1<<2)) + parse_xsb_sound(xsb, sound_offset, name_offset,sf); + else + parse_xsb_variation(xsb, sound_offset, name_offset,sf); + if (xsb->parse_done) break; + } + + return 1; +} + +/** + * XWB "wave bank" has streams (channels, loops, etc), while XSB "sound bank" has cues/sounds + * (volume, pitch, name, etc). Each XSB cue/sound has a variable size and somewhere inside may + * be the stream/wavebank index (some cues are just commands, though). + * + * We want to find a cue pointing to our current wave to get the name. Cues may point to + * multiple streams out of order, and a stream can be used by multiple cues: + * - name 1: simple cue 1 > simple sound 2 > xwb stream 3 + * - name 2: simple cue 2 > complex sound 1 > clip 1/2/3 > xwb streams 4/5/5 + * - name 3: complex cue 1 > simple sound 3 > xwb stream 0 + * - name 4: complex cue 2 > variation > xwb stream 1 + * - name 5: complex cue 3 > variation > simple sound 4/5 > xwb streams 0/1 + * - etc + * Names are optional (almost always included though), and some cues don't have a name + * even if others do. Some offsets are optional, usually signaled by -1/wrong values. + * + * More info: + * - https://wiki.multimedia.cx/index.php/XACT + * - https://github.com/MonoGame/MonoGame/blob/master/MonoGame.Framework/Audio/Xact/ + * - https://github.com/espes/MacTerrariaWrapper/tree/master/xactxtract + */ +static int parse_xsb(xsb_header * xsb, STREAMFILE *sf, char *xwb_wavebank_name) { + int32_t (*read_s32)(off_t,STREAMFILE*) = NULL; + int16_t (*read_s16)(off_t,STREAMFILE*) = NULL; + + + /* check header */ + if ((read_u32be(0x00,sf) != 0x5344424B) && /* "SDBK" (LE) */ + (read_u32be(0x00,sf) != 0x4B424453)) /* "KBDS" (BE) */ + goto fail; + + xsb->big_endian = (read_u32be(0x00,sf) == 0x4B424453); /* "KBDS" */ + read_s32 = xsb->big_endian ? read_s32be : read_s32le; + read_s16 = xsb->big_endian ? read_s16be : read_s16le; + + + /* parse sound bank header */ + xsb->version = read_s16(0x04,sf); /* tool version */ + if (xsb->version <= XSB_XACT1_0_MAX) { + /* 06(2): crc */ + xsb->wavebanks_offset = read_s32(0x08,sf); + /* 0c(4): unknown1 offset (entry: 0x04) */ + /* 10(4): unknown2 offset */ + /* 14(2): element count? */ + /* 16(2): empty? */ + /* 18(2): empty? */ + xsb->complex_cues_count = read_s16(0x1a,sf); + xsb->simple_cues_count = read_s16(0x1c,sf); + xsb->wavebanks_count = read_s16(0x1e,sf); + /* 20(10): xsb name */ + + xsb->sounds_offset = 0x30; + xsb->wavebanks_name_size = 0x10; + } + else if (xsb->version <= XSB_XACT1_1_MAX) { + /* 06(2): crc */ + xsb->wavebanks_offset = read_s32(0x08,sf); + /* 0c(4): unknown1 offset (entry: 0x04) */ + /* 10(4): unknown2 offset */ + /* 14(4): unknown3 offset */ + /* 18(2): empty? */ + /* 1a(2): element count? */ + xsb->complex_cues_count = read_s16(0x1c,sf); + xsb->simple_cues_count = read_s16(0x1e,sf); + /* 20(2): unknown count? (related to unknown2?) */ + xsb->wavebanks_count = read_s16(0x22,sf); + /* 24(10): xsb name */ + + xsb->sounds_offset = 0x34; + xsb->wavebanks_name_size = 0x10; + } + else if (xsb->version <= XSB_XACT1_2_MAX) { + /* 06(2): crc */ + xsb->wavebanks_offset = read_s32(0x08,sf); + /* 0c(4): unknown1 offset (entry: 0x14) */ + /* 10(4): unknown2 offset (entry: variable) */ + /* 14(4): unknown3 offset */ + /* 18(2): empty? */ + /* 1a(2): element count? */ + xsb->complex_cues_count = read_s16(0x1c,sf); + xsb->simple_cues_count = read_s16(0x1e,sf); + /* 20(2): unknown count? (related to unknown2?) */ + xsb->wavebanks_count = read_s16(0x22,sf); + /* 24(4): null? */ + /* 28(10): xsb name */ + + xsb->sounds_offset = 0x38; + xsb->wavebanks_name_size = 0x10; + } + else if (xsb->version <= XSB_XACT2_MAX) { + /* 06(2): crc */ + /* 08(1): platform? (3=X360) */ + xsb->simple_cues_count = read_s16(0x09,sf); + xsb->complex_cues_count = read_s16(0x0B,sf); + xsb->wavebanks_count = read_s8 (0x11,sf); + xsb->sounds_count = read_s16(0x12,sf); + /* 14(2): unknown */ + xsb->cue_names_size = read_s32(0x16,sf); + xsb->simple_cues_offset = read_s32(0x1a,sf); + xsb->complex_cues_offset = read_s32(0x1e,sf); + xsb->cue_names_offset = read_s32(0x22,sf); + /* 26(4): unknown */ + /* 2a(4): unknown */ + /* 2e(4): unknown */ + xsb->wavebanks_offset = read_s32(0x32,sf); + /* 36(4): cue name hash table offset? */ + xsb->nameoffsets_offset = read_s32(0x3a,sf); + xsb->sounds_offset = read_s32(0x3e,sf); + /* 42(4): unknown */ + /* 46(4): unknown */ + /* 4a(64): xsb name */ + + xsb->wavebanks_name_size = 0x40; + } + else { + /* 06(2): format version */ + /* 08(2): crc (fcs16 checksum of all following data) */ + /* 0a(4): last modified low */ + /* 0e(4): last modified high */ + /* 12(1): platform? (1=PC, 3=X360) */ + xsb->simple_cues_count = read_s16(0x13,sf); + xsb->complex_cues_count = read_s16(0x15,sf); + /* 17(2): unknown count? */ + /* 19(2): element count? (often simple+complex cues, but may be more) */ + xsb->wavebanks_count = read_s8 (0x1b,sf); + xsb->sounds_count = read_s16(0x1c,sf); + xsb->cue_names_size = read_s32(0x1e,sf); + xsb->simple_cues_offset = read_s32(0x22,sf); + xsb->complex_cues_offset = read_s32(0x26,sf); + xsb->cue_names_offset = read_s32(0x2a,sf); + /* 0x2E(4): unknown offset */ + /* 0x32(4): variation tables offset */ + /* 0x36(4): unknown offset */ + xsb->wavebanks_offset = read_s32(0x3a,sf); + /* 0x3E(4): cue name hash table offset (16b each) */ + xsb->nameoffsets_offset = read_s32(0x42,sf); + xsb->sounds_offset = read_s32(0x46,sf); + /* 4a(64): xsb name */ + + xsb->wavebanks_name_size = 0x40; + } + + //;VGM_LOG("XSB header: version=%i\n", xsb->version); + //;VGM_LOG("XSB header: count: simple=%i, complex=%i, wavebanks=%i, sounds=%i\n", + // xsb->simple_cues_count, xsb->complex_cues_count, xsb->wavebanks_count, xsb->sounds_count); + //;VGM_LOG("XSB header: offset: simple=%lx, complex=%lx, wavebanks=%lx, sounds=%lx\n", + // xsb->simple_cues_offset, xsb->complex_cues_offset, xsb->wavebanks_offset, xsb->sounds_offset); + //;VGM_LOG("XSB header: names: cues=%lx, size=%x, hash=%lx\n", + // xsb->cue_names_offset, xsb->cue_names_size, xsb->nameoffsets_offset); + + if (xsb->version > XSB_XACT1_2_MAX && xsb->cue_names_size <= 0) { + VGM_LOG("XSB: no names found\n"); + return 1; + } + + + /* find target wavebank */ + if (xsb->wavebanks_count) { + char xsb_wavebank_name[64+1]; + int i; + off_t offset; + + xsb->selected_wavebank = -1; + + offset = xsb->wavebanks_offset; + for (i = 0; i < xsb->wavebanks_count; i++) { + read_string(xsb_wavebank_name,xsb->wavebanks_name_size, offset,sf); + //;VGM_LOG("XSB wavebanks: bank %i=%s\n", i, wavebank_name); + if (strcasecmp(xsb_wavebank_name, xwb_wavebank_name)==0) { + //;VGM_LOG("XSB banks: current xwb is wavebank %i=%s\n", i, xsb_wavebank_name); + xsb->selected_wavebank = i; + } + + offset += xsb->wavebanks_name_size; + } + + //;VGM_LOG("xsb: selected wavebank=%i\n", xsb->selected_wavebank); + if (xsb->selected_wavebank == -1) { + VGM_LOG("XSB: current wavebank not found, selecting first\n"); + xsb->selected_wavebank = 0; //todo goto fail? + } + } + + + /* find cue pointing to stream */ + if (xsb->version <= XSB_XACT1_2_MAX) { + parse_xsb_cues_old(xsb, sf); + } + else { + parse_xsb_cues_new(xsb, sf); + } + + return 1; +fail: + return 0; +} + +static STREAMFILE * open_xsb_filename_pair(STREAMFILE *streamXwb) { + STREAMFILE *streamXsb = NULL; + /* .xwb to .xsb name conversion, since often they don't match */ + static const char *const filename_pairs[][2] = { + {"MUSIC.xwb","Everything.xsb"}, /* Unreal Championship (Xbox) */ + {"Music.xwb","Sound Bank.xsb"}, /* Stardew Valley (Vita) */ + {"Ambiences_intro.xwb","Ambiences.xsb"}, /* Arx Fatalis (Xbox) */ + {"Wave*.xwb","Sound*.xsb"}, /* XNA/MonoGame games? */ + {"*MusicBank.xwb","*SoundBank.xsb"}, /* NFL Fever 2004 (Xbox) */ + {"*_xwb","*_xsb"}, /* Ikaruga (PC) */ + {"WB_*","SB_*"}, /* Ikaruga (X360) */ + {"*StreamBank.xwb","*SoundBank.xsb"}, /* Eschatos (X360) */ + {"*WaveBank.xwb","*SoundBank.xsb"}, /* Eschatos (X360) */ + {"StreamBank_*.xwb","SoundBank_*.xsb"}, /* Ginga Force (X360) */ + {"WaveBank_*.xwb","SoundBank_*.xsb"}, /* Ginga Force (X360) */ + {"*_WB.xwb","*_SB.xsb"}, /* Ninja Blade (X360) */ + {"*.xwb","*.xsb"}, /* default */ + }; + int i; + int pair_count = (sizeof(filename_pairs) / sizeof(filename_pairs[0])); + char target_filename[PATH_LIMIT]; + char temp_filename[PATH_LIMIT]; + int target_len; + + /* try names in external .xsb, using a bunch of possible name pairs */ + get_streamfile_filename(streamXwb,target_filename,PATH_LIMIT); + target_len = strlen(target_filename); + + for (i = 0; i < pair_count; i++) { + const char *xwb_match = filename_pairs[i][0]; + const char *xsb_match = filename_pairs[i][1]; + size_t xwb_len = strlen(xwb_match); + size_t xsb_len = strlen(xsb_match); + int match_pos1 = -1, match_pos2 = -1, xwb_pos = -1 , xsb_pos = -1, new_len = 0; + const char * teststr; + + + //;VGM_LOG("XSB: pair1 '%s'='%s' << '%s' \n", xwb_match, xsb_match, target_filename); + if (target_len < xwb_len) + continue; + + /* ghetto string wildcard replace, ex: + * - target filename = "start1_wildcard_end1", xwb_match = "start1_*_end1", xsb_match = "start2_*_end2" + * > check xwb's "start_" starts in target_filename (from 0..xwb_pos), set match_pos1 + * > check xwb's "_end" ends in target_filename (from xwb_pos+1..end), set match_pos2 + * > copy xsb's "start2_" (from 0..xsb_pos) + * > copy target "wildcard" (from 0..xsb_pos) + * > copy xsb's "end" (from xsb_pos+1..end) + * > final target_filename is "start2_wildcard_end2" + * (skips start/end if wildcard is at start/end) + */ + + teststr = strchr(xwb_match, '*'); + if (teststr) + xwb_pos = (intptr_t)teststr - (intptr_t)xwb_match; + teststr = strchr(xsb_match, '*'); + if (teststr) + xsb_pos = (intptr_t)teststr - (intptr_t)xsb_match; + + match_pos1 = 0; + match_pos2 = target_len; + temp_filename[0] = '\0'; + + if (xwb_pos < 0) { /* no wildcard, check exact match */ + if (target_len != xwb_len || strncmp(target_filename, xwb_match, xwb_len)) + continue; + strcpy(target_filename, xsb_match); + } + + if (xwb_pos > 0) { /* wildcard after start, check starts_with */ + int starts_len = xwb_pos; + if (strncmp(target_filename + 0, xwb_match + 0, xwb_pos) != 0) + continue; + match_pos1 = 0 + starts_len; + } + + if (xwb_pos >= 0 && xwb_pos + 1 < xwb_len) { /* wildcard before end, check ends_with */ + int ends_len = xwb_len - (xwb_pos+1); + if (strncmp(target_filename + target_len - ends_len, xwb_match + xwb_len - ends_len, ends_len) != 0) + continue; + match_pos2 = target_len - ends_len; + } + + if (match_pos1 >= 0 && match_pos2 > match_pos1) { /* save match */ + int match_len = match_pos2 - match_pos1; + strncpy(temp_filename, target_filename + match_pos1, match_len); + temp_filename[match_len] = '\0'; + } + + if (xsb_pos > 0) { /* copy xsb start */ + strncpy(target_filename + 0, xsb_match, (xsb_pos)); + new_len += (xsb_pos); + target_filename[new_len] = '\0'; + } + + if (xsb_pos >= 0){ /* copy xsb match */ + strncpy(target_filename + new_len, temp_filename, (match_pos2 - match_pos1)); + new_len += (match_pos2 - match_pos1); + target_filename[new_len] = '\0'; + } + + if (xsb_pos >= 0 && xsb_pos + 1 < xsb_len) { /* copy xsb end */ + strncpy(target_filename + new_len, xsb_match + (xsb_pos+1), (xsb_len - (xsb_pos+1))); + new_len += (xsb_len - (xsb_pos+1)); + target_filename[new_len] = '\0'; + } + + //;VGM_LOG("XSB: pair2 '%s'='%s' >> '%s'\n", xwb_match, xsb_match, target_filename); + streamXsb = open_streamfile_by_filename(streamXwb, target_filename); + if (streamXsb) return streamXsb; + + get_streamfile_filename(streamXwb,target_filename,PATH_LIMIT); /* reset for next loop */ + } + + return NULL; +} + +#endif /* _XWB_XSB_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xwma_konami.c b/Frameworks/vgmstream/vgmstream/src/meta/xwma_konami.c new file mode 100644 index 000000000..350602a33 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/xwma_konami.c @@ -0,0 +1,86 @@ +#include "meta.h" +#include "../coding/coding.h" +#include "xwma_konami_streamfile.h" + + +/* MSFC - Konami (Armature?) variation [Metal Gear Solid 2 HD (X360), Metal Gear Solid 3 HD (X360)] */ +VGMSTREAM * init_vgmstream_xwma_konami(STREAMFILE *streamFile) { + VGMSTREAM * vgmstream = NULL; + off_t start_offset; + int loop_flag, channel_count, codec, sample_rate; + size_t data_size; + STREAMFILE *temp_streamFile = NULL; + + + /* checks */ + if (!check_extensions(streamFile,"xwma")) + goto fail; + if (read_32bitBE(0x00,streamFile) != 0x58574D41) /* "XWMA" */ + goto fail; + + codec = read_32bitBE(0x04,streamFile); + channel_count = read_32bitBE(0x08,streamFile); + sample_rate = read_32bitBE(0x0c,streamFile); + data_size = read_32bitBE(0x10,streamFile); /* data size without padding */ + loop_flag = 0; + start_offset = 0x20; + + + /* build the VGMSTREAM */ + vgmstream = allocate_vgmstream(channel_count, loop_flag); + if (!vgmstream) goto fail; + + vgmstream->sample_rate = sample_rate; + vgmstream->meta_type = meta_XWMA_KONAMI; + +#ifdef VGM_USE_FFMPEG + { + uint8_t buf[0x100]; + int bytes, avg_bps, block_align; + + /* 0x10: related to size? */ + avg_bps = read_32bitBE(0x14, streamFile); + block_align = read_32bitBE(0x18, streamFile); + + /* data has padding (unrelated to KCEJ blocks) */ + temp_streamFile = setup_xwma_konami_streamfile(streamFile, start_offset, block_align); + if (!temp_streamFile) goto fail; + + bytes = ffmpeg_make_riff_xwma(buf,0x100, codec, data_size, channel_count, sample_rate, avg_bps, block_align); + vgmstream->codec_data = init_ffmpeg_header_offset(temp_streamFile, buf,bytes, 0x00,data_size); + if (!vgmstream->codec_data) goto fail; + vgmstream->coding_type = coding_FFmpeg; + vgmstream->layout_type = layout_none; + + /* manually find total samples */ + { + ms_sample_data msd = {0}; + + msd.channels = vgmstream->channels; + msd.data_offset = 0x00; + msd.data_size = data_size; + + + if (codec == 0x0161) + wma_get_samples(&msd, temp_streamFile, block_align, vgmstream->sample_rate,0x001F); + //else //todo not correct + // wmapro_get_samples(&msd, temp_streamFile, block_align, vgmstream->sample_rate,0x00E0); + + vgmstream->num_samples = msd.num_samples; + if (vgmstream->num_samples == 0) + vgmstream->num_samples = (int32_t)((ffmpeg_codec_data*)vgmstream->codec_data)->totalSamples; /* from avg-br */ + //num_samples seem to be found in the last "seek" table entry too, as: entry / channels / 2 + } + } +#else + goto fail; +#endif + + close_streamfile(temp_streamFile); + return vgmstream; + +fail: + close_streamfile(temp_streamFile); + close_vgmstream(vgmstream); + return NULL; +} diff --git a/Frameworks/vgmstream/vgmstream/src/meta/xwma_konami_streamfile.h b/Frameworks/vgmstream/vgmstream/src/meta/xwma_konami_streamfile.h new file mode 100644 index 000000000..d95df4594 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/meta/xwma_konami_streamfile.h @@ -0,0 +1,125 @@ +#ifndef _XWMA_KONAMI_STREAMFILE_H_ +#define _XWMA_KONAMI_STREAMFILE_H_ +#include "../streamfile.h" + + +typedef struct { + /* config */ + off_t stream_offset; + size_t stream_size; + size_t block_align; + + /* state */ + off_t logical_offset; /* fake offset */ + off_t physical_offset; /* actual offset */ + size_t block_size; /* current size */ + size_t skip_size; /* size from block start to reach data */ + size_t data_size; /* usable size in a block */ + + size_t logical_size; +} xwma_konami_io_data; + + +static size_t xwma_konami_io_read(STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length, xwma_konami_io_data* data) { + size_t total_read = 0; + + + /* re-start when previous offset (can't map logical<>physical offsets) */ + if (data->logical_offset < 0 || offset < data->logical_offset) { + data->physical_offset = data->stream_offset; + data->logical_offset = 0x00; + data->data_size = 0; + } + + /* read blocks */ + while (length > 0) { + + /* ignore EOF */ + if (offset < 0 || data->physical_offset >= data->stream_offset + data->stream_size) { + break; + } + + /* process new block */ + if (data->data_size == 0) { + data->block_size = align_size_to_block(data->block_align, 0x10); + data->data_size = data->block_align; + data->skip_size = 0x00; + } + + /* move to next block */ + if (data->data_size == 0 || offset >= data->logical_offset + data->data_size) { + data->physical_offset += data->block_size; + data->logical_offset += data->data_size; + data->data_size = 0; + continue; + } + + /* read data */ + { + size_t bytes_consumed, bytes_done, to_read; + + bytes_consumed = offset - data->logical_offset; + to_read = data->data_size - bytes_consumed; + if (to_read > length) + to_read = length; + bytes_done = read_streamfile(dest, data->physical_offset + data->skip_size + bytes_consumed, to_read, streamfile); + + total_read += bytes_done; + dest += bytes_done; + offset += bytes_done; + length -= bytes_done; + + if (bytes_done != to_read || bytes_done == 0) { + break; /* error/EOF */ + } + } + } + + return total_read; +} + +static size_t xwma_konami_io_size(STREAMFILE *streamfile, xwma_konami_io_data* data) { + uint8_t buf[1]; + + if (data->logical_size) + return data->logical_size; + + /* force a fake read at max offset, to get max logical_offset (will be reset next read) */ + xwma_konami_io_read(streamfile, buf, 0x7FFFFFFF, 1, data); + data->logical_size = data->logical_offset; + + return data->logical_size; +} + +/* Handles de-padding Konami XWMA blocked streams */ +static STREAMFILE* setup_xwma_konami_streamfile(STREAMFILE *streamFile, off_t stream_offset, size_t block_align) { + STREAMFILE *temp_streamFile = NULL, *new_streamFile = NULL; + xwma_konami_io_data io_data = {0}; + size_t io_data_size = sizeof(xwma_konami_io_data); + + io_data.stream_offset = stream_offset; + io_data.stream_size = get_streamfile_size(streamFile) - stream_offset; + io_data.block_align = block_align; + io_data.logical_offset = -1; /* force phys offset reset */ + + /* setup subfile */ + new_streamFile = open_wrap_streamfile(streamFile); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_io_streamfile(new_streamFile, &io_data,io_data_size, xwma_konami_io_read,xwma_konami_io_size); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + new_streamFile = open_buffer_streamfile(new_streamFile,0); + if (!new_streamFile) goto fail; + temp_streamFile = new_streamFile; + + return temp_streamFile; + +fail: + close_streamfile(temp_streamFile); + return NULL; +} + +#endif /* _XWMA_KONAMI_STREAMFILE_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/mixing.c b/Frameworks/vgmstream/vgmstream/src/mixing.c new file mode 100644 index 000000000..81ceffbcc --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/mixing.c @@ -0,0 +1,982 @@ +#include "vgmstream.h" +#include "mixing.h" +#include "plugins.h" +#include +#include + + +/** + * Mixing lets vgmstream modify the resulting sample buffer before final output. + * This can be implemented in a number of ways but it's done like it is considering + * overall simplicity in coding, usage and performance (main complexity is allowing + * down/upmixing). Code is mostly independent with some hooks in the main vgmstream + * code. + * + * It works using two buffers: + * - outbuf: plugin's pcm16 buffer, at least input_channels*sample_count + * - mixbuf: internal's pcmfloat buffer, at least mixing_channels*sample_count + * outbuf starts with decoded samples of vgmstream->channel size. This unsures that + * if no mixing is done (most common case) we can skip copying samples between buffers. + * Resulting outbuf after mixing has samples for ->output_channels (plus garbage). + * - output_channels is the resulting total channels (that may be less/more/equal) + * - input_channels is normally ->channels or ->output_channels when it's higher + * + * First, a meta (ex. TXTP) or plugin may add mixing commands through the API, + * validated so non-sensical mixes are ignored (to ensure mixing code doesn't + * have to recheck every time). Then, before starting to decode mixing must be + * manually activated, because plugins need to be ready for possibly different + * input/output channels. API could be improved but this way we can avoid having + * to update all plugins, while allowing internal setup and layer/segment mixing + * (may change in the future for simpler usage). + * + * Then after decoding normally, vgmstream applies mixing internally: + * - detect if mixing is active and needs to be done at this point (some effects + * like fades only apply after certain time) and skip otherwise. + * - copy outbuf to mixbuf, as using a float buffer to increase accuracy (most ops + * apply float volumes) and slightly improve performance (avoids doing + * int16-to-float casts per mix, as it's not free) + * - apply all mixes on mixbuf + * - copy mixbuf to outbuf + * segmented/layered layouts handle mixing on their own. + * + * Mixing is tuned for most common case (no mix except fade-out at the end) and is + * fast enough but not super-optimized yet, there is some penalty the more effects + * are applied. Maybe could add extra sub-ops to avoid ifs and dumb values (volume=0.0 + * could simply use a clear op), only use mixbuf if necessary (swap can be done without + * mixbuf if it goes first) or add function pointer indexes but isn't too important. + * Operations are applied once per "step" with 1 sample from all channels to simplify code + * (and maybe improve memory cache?), though maybe it should call one function per operation. + */ + +#define VGMSTREAM_MAX_MIXING 128 + + +/* mixing info */ +typedef enum { + MIX_SWAP, + MIX_ADD, + MIX_VOLUME, + MIX_LIMIT, + MIX_UPMIX, + MIX_DOWNMIX, + MIX_KILLMIX, + MIX_FADE +} mix_command_t; + +typedef struct { + mix_command_t command; + /* common */ + int ch_dst; + int ch_src; + float vol; + + /* fade envelope */ + float vol_start; /* volume from pre to start */ + float vol_end; /* volume from end to post */ + char shape; /* curve type */ + int32_t time_pre; /* position before time_start where vol_start applies (-1 = beginning) */ + int32_t time_start; /* fade start position where vol changes from vol_start to vol_end */ + int32_t time_end; /* fade end position where vol changes from vol_start to vol_end */ + int32_t time_post; /* position after time_end where vol_end applies (-1 = end) */ +} mix_command_data; + +typedef struct { + int mixing_channels; /* max channels needed to mix */ + int output_channels; /* resulting channels after mixing */ + int mixing_on; /* mixing allowed */ + int mixing_count; /* mixing number */ + size_t mixing_size; /* mixing max */ + mix_command_data mixing_chain[VGMSTREAM_MAX_MIXING]; /* effects to apply (could be alloc'ed but to simplify...) */ + float* mixbuf; /* internal mixing buffer */ +} mixing_data; + + +/* ******************************************************************* */ + +static int is_active(mixing_data *data, int32_t current_start, int32_t current_end) { + int i; + int32_t fade_start, fade_end; + + for (i = 0; i < data->mixing_count; i++) { + mix_command_data mix = data->mixing_chain[i]; + + if (mix.command != MIX_FADE) + return 1; /* has non-fades = active */ + + /* check is current range falls within a fade + * (assuming fades were already optimized on add) */ + fade_start = mix.time_pre < 0 ? 0 : mix.time_pre; + fade_end = mix.time_post < 0 ? INT_MAX : mix.time_post; + + if (current_start < fade_end && current_end > fade_start) + return 1; + } + + return 0; +} + +static int32_t get_current_pos(VGMSTREAM* vgmstream) { + int32_t current_pos; + + if (vgmstream->loop_flag && vgmstream->current_sample > vgmstream->loop_start_sample) { + int loop_pre = vgmstream->loop_start_sample; + int loop_into = vgmstream->current_sample - vgmstream->loop_start_sample; + int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample; + current_pos = loop_pre + loop_into + loop_samples*vgmstream->loop_count; + } + else { + current_pos = vgmstream->current_sample; + } + + return current_pos; +} + +static int get_fade_gain(mix_command_data *mix, float *out_cur_vol, int32_t current_subpos) { + //todo optimizations: interleave calcs, maybe use cosf, powf, etc? + float cur_vol = 0.0f; + + if ((current_subpos >= mix->time_pre || mix->time_pre < 0) && current_subpos < mix->time_start) { + cur_vol = mix->vol_start; /* before */ + } + else if (current_subpos >= mix->time_end && (current_subpos < mix->time_post || mix->time_post < 0)) { + cur_vol = mix->vol_end; /* after */ + } + else if (current_subpos >= mix->time_start && current_subpos < mix->time_end) { + /* in between */ + float range_vol, range_dur, range_idx, index, gain; + + if (mix->vol_start < mix->vol_end) { /* fade in */ + range_vol = mix->vol_end - mix->vol_start; + range_dur = mix->time_end - mix->time_start; + range_idx = current_subpos - mix->time_start; + index = range_idx / range_dur; + } else { /* fade out */ + range_vol = mix->vol_end - mix->vol_start; + range_dur = mix->time_end - mix->time_start; + range_idx = mix->time_end - current_subpos; + index = range_idx / range_dur; + } + + /* Fading is done like this: + * - find current position within fade duration + * - get linear % (or rather, index from 0.0 .. 1.0) of duration + * - apply shape to % (from linear fade to curved fade) + * - get final volume for that point + * + * Roughly speaking some curve shapes are better for fades (decay rate is more natural + * sounding in that highest to mid/low happens faster but low to lowest takes more time, + * kinda like a gunshot or bell), and others for crossfades (decay of fade-in + fade-out + * is adjusted so that added volume level stays constant-ish). + * + * As curves can fade in two ways ('normal' and curving 'the other way'), they are adjusted + * to get 'normal' shape on both fades (by reversing index and making 1 - gain), thus some + * curves are complementary (exponential fade-in ~= logarithmic fade-out); the following + * are described taking fade-in = normal. + */ + + /* (curve math mostly from SoX/FFmpeg) */ + switch(mix->shape) { + /* 2.5f in L/E 'pow' is the attenuation factor, where 5.0 (100db) is common but a bit fast + * (alt calculations with 'exp' from FFmpeg use (factor)*ln(0.1) = -NN.N... */ + + case 'E': /* exponential (for fade-outs, closer to natural decay of sound) */ + //gain = pow(0.1f, (1.0f - index) * 2.5f); + gain = exp(-5.75646273248511f * (1.0f - index)); + break; + case 'L': /* logarithmic (inverse of the above, maybe for crossfades) */ + //gain = 1 - pow(0.1f, (index) * 2.5f); + gain = 1 - exp(-5.75646273248511f * (index)); + break; + + case 'H': /* raised sine wave or cosine wave (for more musical crossfades) */ + gain = (1.0f - cos(index * M_PI )) / 2.0f; + break; + + case 'Q': /* quarter of sine wave (for musical fades) */ + gain = sin(index * M_PI / 2.0f); + break; + + case 'p': /* parabola (maybe for crossfades) */ + gain = 1.0f - sqrt(1.0f - index); + break; + case 'P': /* inverted parabola (maybe for fades) */ + gain = (1.0f - (1.0f - index) * (1.0f - index)); + break; + + case 'T': /* triangular/linear (simpler/sharper fades) */ + default: + gain = index; + break; + } + + if (mix->vol_start < mix->vol_end) { /* fade in */ + cur_vol = mix->vol_start + range_vol * gain; + } else { /* fade out */ + cur_vol = mix->vol_end - range_vol * gain; //mix->vol_start - range_vol * (1 - gain); + } + } + else { + goto fail; + } + + *out_cur_vol = cur_vol; + return 1; +fail: + return 0; +} + +void mix_vgmstream(sample_t *outbuf, int32_t sample_count, VGMSTREAM* vgmstream) { + mixing_data *data = vgmstream->mixing_data; + int ch, s, m, ok; + + int32_t current_pos, current_subpos; + float temp_f, temp_min, temp_max, cur_vol; + float *temp_mixbuf; + sample_t *temp_outbuf; + + const float limiter_max = 32767.0f; + const float limiter_min = -32768.0f; + + + /* no support or not need to apply */ + if (!data || !data->mixing_on || data->mixing_count == 0) + return; + + /* try to skip if no ops apply (for example if fade set but does nothing yet) */ + current_pos = get_current_pos(vgmstream); + if (!is_active(data, current_pos, current_pos + sample_count)) + return; + + + /* use advancing buffer pointers to simplify logic */ + temp_mixbuf = data->mixbuf; + temp_outbuf = outbuf; + + /* apply mixes in order per channel */ + for (s = 0; s < sample_count; s++) { + /* reset after new sample 'step'*/ + float *stpbuf = temp_mixbuf; + int step_channels = vgmstream->channels; + + for (ch = 0; ch < step_channels; ch++) { + stpbuf[ch] = temp_outbuf[ch]; /* copy current 'lane' */ + } + + for (m = 0; m < data->mixing_count; m++) { + mix_command_data mix = data->mixing_chain[m]; + + /* mixing ops are designed to apply in order, all channels per 1 sample 'step'. Since some ops change + * total channels, channel number meaning varies as ops move them around, ex: + * - 4ch w/ "1-2,2+3" = ch1<>ch3, ch2(old ch1)+ch3 = 4ch: ch2 ch1+ch3 ch3 ch4 + * - 4ch w/ "2+3,1-2" = ch2+ch3, ch1<>ch2(modified) = 4ch: ch2+ch3 ch1 ch3 ch4 + * - 2ch w/ "1+2,1u" = ch1+ch2, ch1(add and push rest) = 3ch: ch1' ch1+ch2 ch2 + * - 2ch w/ "1u,1+2" = ch1(add and push rest) = 3ch: ch1'+ch1 ch1 ch2 + * - 2ch w/ "1-2,1d" = ch1<>ch2, ch1(drop and move ch2(old ch1) to ch1) = ch1 + * - 2ch w/ "1d,1-2" = ch1(drop and pull rest), ch1(do nothing, ch2 doesn't exist now) = ch2 + */ + switch(mix.command) { + + case MIX_SWAP: + temp_f = stpbuf[mix.ch_dst]; + stpbuf[mix.ch_dst] = stpbuf[mix.ch_src]; + stpbuf[mix.ch_src] = temp_f; + break; + + case MIX_ADD: + stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] + stpbuf[mix.ch_src] * mix.vol; + break; + + case MIX_VOLUME: + if (mix.ch_dst < 0) { + for (ch = 0; ch < step_channels; ch++) { + stpbuf[ch] = stpbuf[ch] * mix.vol; + } + } + else { + stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] * mix.vol; + } + break; + + case MIX_LIMIT: + temp_max = limiter_max * mix.vol; + temp_min = limiter_min * mix.vol; + + if (mix.ch_dst < 0) { + for (ch = 0; ch < step_channels; ch++) { + if (stpbuf[ch] > temp_max) + stpbuf[ch] = temp_max; + else if (stpbuf[ch] < temp_min) + stpbuf[ch] = temp_min; + } + } + else { + if (stpbuf[mix.ch_dst] > temp_max) + stpbuf[mix.ch_dst] = temp_max; + else if (stpbuf[mix.ch_dst] < temp_min) + stpbuf[mix.ch_dst] = temp_min; + } + break; + + case MIX_UPMIX: + step_channels += 1; + for (ch = step_channels - 1; ch > mix.ch_dst; ch--) { + stpbuf[ch] = stpbuf[ch-1]; /* 'push' channels forward (or pull backwards) */ + } + stpbuf[mix.ch_dst] = 0; /* inserted as silent */ + break; + + case MIX_DOWNMIX: + step_channels -= 1; + for (ch = mix.ch_dst; ch < step_channels; ch++) { + stpbuf[ch] = stpbuf[ch+1]; /* 'pull' channels back */ + } + break; + + case MIX_KILLMIX: + step_channels = mix.ch_dst; /* clamp channels */ + break; + + case MIX_FADE: + current_subpos = current_pos + s; + + ok = get_fade_gain(&mix, &cur_vol, current_subpos); + if (!ok) { + break; + } + + if (mix.ch_dst < 0) { + for (ch = 0; ch < step_channels; ch++) { + stpbuf[ch] = stpbuf[ch] * cur_vol; + } + } + else { + stpbuf[mix.ch_dst] = stpbuf[mix.ch_dst] * cur_vol; + } + break; + + default: + break; + } + } + + temp_mixbuf += step_channels; + temp_outbuf += vgmstream->channels; + } + + /* copy resulting mix to output */ + for (s = 0; s < sample_count * data->output_channels; s++) { + /* 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 + */ + outbuf[s] = clamp16( (int32_t)data->mixbuf[s] ); + } +} + +/* ******************************************************************* */ + +void mixing_init(VGMSTREAM* vgmstream) { + mixing_data *data = calloc(1, sizeof(mixing_data)); + if (!data) goto fail; + + data->mixing_size = VGMSTREAM_MAX_MIXING; /* fixed array for now */ + data->mixing_channels = vgmstream->channels; + data->output_channels = vgmstream->channels; + + vgmstream->mixing_data = data; + return; + +fail: + free(data); + return; +} + +void mixing_close(VGMSTREAM* vgmstream) { + mixing_data *data = NULL; + if (!vgmstream) return; + + data = vgmstream->mixing_data; + if (!data) return; + + free(data->mixbuf); + free(data); +} + +void mixing_update_channel(VGMSTREAM* vgmstream) { + mixing_data *data = vgmstream->mixing_data; + if (!data) return; + + /* lame hack for dual stereo, but dual stereo is pretty hack-ish to begin with */ + data->mixing_channels++; + data->output_channels++; +} + +/* ******************************************************************* */ + +static int add_mixing(VGMSTREAM* vgmstream, mix_command_data *mix) { + mixing_data *data = vgmstream->mixing_data; + if (!data) return 0; + + + if (data->mixing_on) { + VGM_LOG("MIX: ignoring new mixes when mixing active\n"); + return 0; /* to avoid down/upmixing after activation */ + } + + if (data->mixing_count + 1 > data->mixing_size) { + VGM_LOG("MIX: too many mixes\n"); + return 0; + } + + data->mixing_chain[data->mixing_count] = *mix; /* memcpy */ + data->mixing_count++; + + //;VGM_LOG("MIX: total %i\n", data->mixing_count); + return 1; +} + + +void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + + if (ch_dst < 0 || ch_src < 0 || ch_dst == ch_src) return; + if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return; + mix.command = MIX_SWAP; + mix.ch_dst = ch_dst; + mix.ch_src = ch_src; + + add_mixing(vgmstream, &mix); +} + +void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + if (!data) return; + + //if (volume < 0.0) return; /* negative volume inverts the waveform */ + if (volume == 0.0) return; /* ch_src becomes silent and nothing is added */ + if (ch_dst < 0 || ch_src < 0) return; + if (!data || ch_dst >= data->output_channels || ch_src >= data->output_channels) return; + + mix.command = MIX_ADD; //if (volume == 1.0) MIX_ADD_COPY /* could simplify */ + mix.ch_dst = ch_dst; + mix.ch_src = ch_src; + mix.vol = volume; + + //;VGM_LOG("MIX: add %i+%i*%f\n", ch_dst,ch_src,volume); + add_mixing(vgmstream, &mix); +} + +void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + + //if (ch_dst < 0) return; /* means all channels */ + //if (volume < 0.0) return; /* negative volume inverts the waveform */ + if (volume == 1.0) return; /* no change */ + if (!data || ch_dst >= data->output_channels) return; + + mix.command = MIX_VOLUME; //if (volume == 0.0) MIX_VOLUME0 /* could simplify */ + mix.ch_dst = ch_dst; + mix.vol = volume; + + //;VGM_LOG("MIX: volume %i*%f\n", ch_dst,volume); + add_mixing(vgmstream, &mix); +} + +void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + + //if (ch_dst < 0) return; /* means all channels */ + if (volume < 0.0) return; + if (volume == 1.0) return; /* no actual difference */ + if (!data || ch_dst >= data->output_channels) return; + //if (volume == 0.0) return; /* dumb but whatevs */ + + mix.command = MIX_LIMIT; + mix.ch_dst = ch_dst; + mix.vol = volume; + + add_mixing(vgmstream, &mix); +} + +void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + int ok; + + if (ch_dst < 0) return; + if (!data || ch_dst > data->output_channels || data->output_channels +1 > VGMSTREAM_MAX_CHANNELS) return; + /* dst can be == output_channels here, since we are inserting */ + + mix.command = MIX_UPMIX; + mix.ch_dst = ch_dst; + + ok = add_mixing(vgmstream, &mix); + if (ok) { + data->output_channels += 1; + if (data->mixing_channels < data->output_channels) + data->mixing_channels = data->output_channels; + } +} + +void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + int ok; + + if (ch_dst < 0) return; + if (!data || ch_dst >= data->output_channels || data->output_channels - 1 < 1) return; + + mix.command = MIX_DOWNMIX; + mix.ch_dst = ch_dst; + + ok = add_mixing(vgmstream, &mix); + if (ok) { + data->output_channels -= 1; + } +} + +void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + int ok; + + if (ch_dst <= 0) return; /* can't kill from first channel */ + if (!data || ch_dst >= data->output_channels) return; + + mix.command = MIX_KILLMIX; + mix.ch_dst = ch_dst; + + //;VGM_LOG("MIX: killmix %i\n", ch_dst); + ok = add_mixing(vgmstream, &mix); + if (ok) { + data->output_channels = ch_dst; /* clamp channels */ + } +} + + +static mix_command_data* get_last_fade(mixing_data *data, int target_channel) { + int i; + for (i = data->mixing_count; i > 0; i--) { + mix_command_data *mix = &data->mixing_chain[i-1]; + if (mix->command != MIX_FADE) + continue; + if (mix->ch_dst == target_channel) + return mix; + } + + return NULL; +} + + +void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape, + int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post) { + mixing_data *data = vgmstream->mixing_data; + mix_command_data mix = {0}; + mix_command_data *mix_prev; + + + //if (ch_dst < 0) return; /* means all channels */ + if (!data || ch_dst >= data->output_channels) return; + if (time_pre > time_start || time_start > time_end || (time_post >= 0 && time_end > time_post)) return; + if (time_start < 0 || time_end < 0) return; + //if (time_pre < 0 || time_post < 0) return; /* special meaning of file start/end */ + //if (vol_start == vol_end) /* weird but let in case of being used to cancel others fades... maybe? */ + + if (shape == '{' || shape == '}') + shape = 'E'; + if (shape == '(' || shape == ')') + shape = 'H'; + + mix.command = MIX_FADE; + mix.ch_dst = ch_dst; + mix.vol_start = vol_start; + mix.vol_end = vol_end; + mix.shape = shape; + mix.time_pre = time_pre; + mix.time_start = time_start; + mix.time_end = time_end; + mix.time_post = time_post; + + + /* cancel fades and optimize a bit when using negative pre/post: + * - fades work like this: + * <----------|----------|----------> + * pre1 start1 end1 post1 + * - when pre and post are set nothing is done (fade is exact and multiple fades may overlap) + * - when previous fade's post or current fade's pre are negative (meaning file end/start) + * they should cancel each other (to allow chaning fade-in + fade-out + fade-in + etc): + * <----------|----------|----------| |----------|----------|----------> + * pre1 start1 end1 post1 pre2 start2 end2 post2 + * - other cases (previous fade is actually after/in-between current fade) are ignored + * as they're uncommon and hard to optimize + * fades cancel fades of the same channel, and 'all channel' (-1) fades also cancel 'all channels' + */ + mix_prev = get_last_fade(data, mix.ch_dst); + if (mix_prev == NULL) { + if (vol_start == 1.0 && time_pre < 0) + time_pre = time_start; /* fade-out helds default volume before fade start can be clamped */ + if (vol_end == 1.0 && time_post < 0) + time_post = time_end; /* fade-in helds default volume after fade end can be clamped */ + } + else if (mix_prev->time_post < 0 || mix.time_pre < 0) { + int is_prev = 1; + if ((mix_prev->time_end > mix.time_start) || + (mix_prev->time_post >= 0 && mix_prev->time_post > mix.time_start) || + (mix.time_pre >= 0 && mix.time_pre < mix_prev->time_end)) + is_prev = 0; + + if (is_prev) { + /* change negative values to actual points */ + if (mix_prev->time_post < 0 && mix_prev->time_post < 0) { + mix_prev->time_post = mix_prev->time_end; + mix.time_pre = mix_prev->time_post; + } + if (mix_prev->time_post >= 0 && mix.time_pre < 0) { + + mix.time_pre = mix_prev->time_post; + } + else if (mix_prev->time_post < 0 && mix.time_pre >= 0) { + mix_prev->time_post = mix.time_pre; + } + /* else: both define start/ends, do nothing */ + } + /* should only modify prev if add_mixing but meh */ + } + + //;VGM_LOG("MIX: fade %i^%f~%f=%c@%i~%i~%i~%i\n", ch_dst, vol_start, vol_end, shape, time_pre, time_start, time_end, time_post); + add_mixing(vgmstream, &mix); +} + +/* ******************************************************************* */ + +void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask) { + mixing_data *data = vgmstream->mixing_data; + int ch; + + if (!data) + return; + + if (mask == 0) { + mixing_push_volume(vgmstream, -1, volume); + return; + } + + for (ch = 0; ch < data->output_channels; ch++) { + if (!((mask >> ch) & 1)) + continue; + mixing_push_volume(vgmstream, ch, volume); + } +} + +void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask) { + mixing_data *data = vgmstream->mixing_data; + int ch; + + if (!data) + return; + + if (mask == 0) { + return; + } + + /* reverse remove all channels (easier this way as when removing channels numbers change) */ + for (ch = data->output_channels - 1; ch >= 0; ch--) { + if ((mask >> ch) & 1) + continue; + mixing_push_downmix(vgmstream, ch); + } +} + +void mixing_macro_layer(VGMSTREAM* vgmstream, int max, uint32_t mask, char mode) { + mixing_data *data = vgmstream->mixing_data; + int current, ch, output_channels, selected_channels; + + if (!data) + return; + if (max <= 0 || data->output_channels <= max) + return; + + /* set all channels (non-existant channels will be ignored) */ + if (mask == 0) { + mask = ~mask; + } + + /* save before adding fake channels */ + output_channels = data->output_channels; + + /* count possibly set channels */ + selected_channels = 0; + for (ch = 0; ch < output_channels; ch++) { + selected_channels += (mask >> ch) & 1; + } + + /* make N fake channels at the beginning for easier calcs */ + for (ch = 0; ch < max; ch++) { + mixing_push_upmix(vgmstream, 0); + } + + /* add all layers in this order: ch0: 0, 0+N, 0+N*2 ... / ch1: 1, 1+N ... */ + current = 0; + for (ch = 0; ch < output_channels; ch++) { + double volume = 1.0; + + if (!((mask >> ch) & 1)) + continue; + + /* mode 'v': same volume for all layers (for layered vocals) */ + /* mode 'b': volume adjusted depending on layers (for layered bgm) */ + /* mode 'e': volume adjusted equally for all layers (for generic downmixing) */ + if (mode == 'b' && ch < max) { + /* reduce a bit main channels (see below) */ + int channel_mixes = selected_channels / max; + if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */ + channel_mixes += 1; + channel_mixes -= 1; /* better formula? */ + if (channel_mixes <= 0) /* ??? */ + channel_mixes = 1; + + volume = 1 / sqrt(channel_mixes); + } + if ((mode == 'b' && ch >= max) || (mode == 'e')) { + /* find how many will be mixed in current channel (earlier channels receive more + * mixes than later ones, ex: selected 8ch + max 3ch: ch0=0+3+6, ch1=1+4+7, ch2=2+5) */ + int channel_mixes = selected_channels / max; + if (channel_mixes <= 0) /* ??? */ + channel_mixes = 1; + if (current < selected_channels % (channel_mixes * max)) /* may be simplified? */ + channel_mixes += 1; + + volume = 1 / sqrt(channel_mixes); /* "power" add */ + } + //;VGM_LOG("MIX: layer ch=%i, cur=%i, v=%f\n", ch, current, volume); + + mixing_push_add(vgmstream, current, max + ch, volume); /* ch adjusted considering upmixed channels */ + current++; + if (current >= max) + current = 0; + } + + /* remove all mixed channels */ + mixing_push_killmix(vgmstream, max); +} + +void mixing_macro_crosstrack(VGMSTREAM* vgmstream, int max) { + mixing_data *data = vgmstream->mixing_data; + int current, ch, track, track_ch, track_num, output_channels; + int32_t change_pos, change_next, change_time; + + if (!data) + return; + if (max <= 0 || data->output_channels <= max) + return; + if (!vgmstream->loop_flag) /* maybe force loop? */ + return; + + /* this probably only makes sense for even channels so upmix before if needed) */ + output_channels = data->output_channels; + if (output_channels % 2) { + mixing_push_upmix(vgmstream, output_channels); + output_channels += 1; + } + + /* set loops to hear all track changes */ + track_num = output_channels / max; + if (vgmstream->config_loop_count < track_num) + vgmstream->config_loop_count = track_num; + + ch = 0; + for (track = 0; track < track_num; track++) { + double volume = 1.0; /* won't play at the same time, no volume change needed */ + + int loop_pre = vgmstream->loop_start_sample; + int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample; + change_pos = loop_pre + loop_samples * track; + change_next = loop_pre + loop_samples * (track + 1); + change_time = 15.0 * vgmstream->sample_rate; /* in secs */ + + for (track_ch = 0; track_ch < max; track_ch++) { + if (track > 0) { /* fade-in when prev track fades-out */ + mixing_push_fade(vgmstream, ch + track_ch, 0.0, volume, '(', -1, change_pos, change_pos + change_time, -1); + } + + if (track + 1 < track_num) { /* fade-out when next track fades-in */ + mixing_push_fade(vgmstream, ch + track_ch, volume, 0.0, ')', -1, change_next, change_next + change_time, -1); + } + } + + ch += max; + } + + /* mix all tracks into first */ + current = 0; + for (ch = max; ch < output_channels; ch++) { + mixing_push_add(vgmstream, current, ch, 1.0); /* won't play at the same time, no volume change needed */ + + current++; + if (current >= max) + current = 0; + } + + /* remove unneeded channels */ + mixing_push_killmix(vgmstream, max); +} + +void mixing_macro_crosslayer(VGMSTREAM* vgmstream, int max, char mode) { + mixing_data *data = vgmstream->mixing_data; + int current, ch, layer, layer_ch, layer_num, loop, output_channels; + int32_t change_pos, change_time; + + if (!data) + return; + if (max <= 0 || data->output_channels <= max) + return; + if (!vgmstream->loop_flag) /* maybe force loop? */ + return; + + /* this probably only makes sense for even channels so upmix before if needed) */ + output_channels = data->output_channels; + if (output_channels % 2) { + mixing_push_upmix(vgmstream, output_channels); + output_channels += 1; + } + + /* set loops to hear all track changes */ + layer_num = output_channels / max; + if (vgmstream->config_loop_count < layer_num) + vgmstream->config_loop_count = layer_num; + + /* mode 'v': constant volume + * mode 'e': sets fades to successively lower/equalize volume per loop for each layer + * (to keep final volume constant-ish), ex. 3 layers/loops, 2 max: + * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]-- + * - layer1 (ch2+3): loop0 --[0.0]--, loop1 (=0.0..0.7, loop2 )=0.7..0.5, loop3 --[0.5/end]-- + * - layer2 (ch4+5): loop0 --[0.0]--, loop1 ---[0.0]--, loop2 (=0.0..0.5, loop3 --[0.5/end]-- + * mode 'b': similar but 1st layer (main) has higher/delayed volume: + * - layer0 (ch0+1): loop0 --[1.0]--, loop1 )=1.0..1.0, loop2 )=1.0..0.7, loop3 --[0.7/end]-- + */ + for (loop = 1; loop < layer_num; loop++) { + double volume1 = 1.0; + double volume2 = 1.0; + + int loop_pre = vgmstream->loop_start_sample; + int loop_samples = vgmstream->loop_end_sample - vgmstream->loop_start_sample; + change_pos = loop_pre + loop_samples * loop; + change_time = 10.0 * vgmstream->sample_rate; /* in secs */ + + if (mode == 'e') { + volume1 = 1 / sqrt(loop + 0); + volume2 = 1 / sqrt(loop + 1); + } + + ch = 0; + for (layer = 0; layer < layer_num; layer++) { + char type; + + if (mode == 'b') { + if (layer == 0) { + volume1 = 1 / sqrt(loop - 1 <= 0 ? 1 : loop - 1); + volume2 = 1 / sqrt(loop + 0); + } + else { + volume1 = 1 / sqrt(loop + 0); + volume2 = 1 / sqrt(loop + 1); + } + } + + if (layer > loop) { /* not playing yet (volume is implicitly 0.0 from first fade in) */ + continue; + } else if (layer == loop) { /* fades in for the first time */ + volume1 = 0.0; + type = '('; + } else { /* otherwise fades out to match other layers's volume */ + type = ')'; + } + + //;VGM_LOG("MIX: loop=%i, layer %i, vol1=%f, vol2=%f\n", loop, layer, volume1, volume2); + + for (layer_ch = 0; layer_ch < max; layer_ch++) { + mixing_push_fade(vgmstream, ch + layer_ch, volume1, volume2, type, -1, change_pos, change_pos + change_time, -1); + } + + ch += max; + } + } + + /* mix all tracks into first */ + current = 0; + for (ch = max; ch < output_channels; ch++) { + mixing_push_add(vgmstream, current, ch, 1.0); + + current++; + if (current >= max) + current = 0; + } + + /* remove unneeded channels */ + mixing_push_killmix(vgmstream, max); +} + +/* ******************************************************************* */ + +void mixing_setup(VGMSTREAM * vgmstream, int32_t max_sample_count) { + mixing_data *data = vgmstream->mixing_data; + float *mixbuf_re = NULL; + + if (!data) goto fail; + + /* a bit wonky but eh... */ + if (vgmstream->channel_layout && vgmstream->channels != data->output_channels) { + vgmstream->channel_layout = 0; + ((VGMSTREAM*)vgmstream->start_vgmstream)->channel_layout = 0; + } + + /* special value to not actually enable anything (used to query values) */ + if (max_sample_count <= 0) + goto fail; + + /* create or alter internal buffer */ + mixbuf_re = realloc(data->mixbuf, max_sample_count*data->mixing_channels*sizeof(float)); + if (!mixbuf_re) goto fail; + + data->mixbuf = mixbuf_re; + data->mixing_on = 1; + + /* since data exists on its own memory and pointer is already set + * there is no need to propagate to start_vgmstream */ + + /* segments/layers are independant from external buffers and may always mix */ + + return; +fail: + return; +} + +void mixing_info(VGMSTREAM * vgmstream, int *out_input_channels, int *out_output_channels) { + mixing_data *data = vgmstream->mixing_data; + int input_channels, output_channels; + + if (!data) goto fail; + + output_channels = data->output_channels; + if (data->output_channels > vgmstream->channels) + input_channels = data->output_channels; + else + input_channels = vgmstream->channels; + + if (out_input_channels) *out_input_channels = input_channels; + if (out_output_channels) *out_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: + return; +} diff --git a/Frameworks/vgmstream/vgmstream/src/mixing.h b/Frameworks/vgmstream/vgmstream/src/mixing.h new file mode 100644 index 000000000..98e0b2e18 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/mixing.h @@ -0,0 +1,41 @@ +#ifndef _MIXING_H_ +#define _MIXING_H_ + +#include "vgmstream.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); + +/* internal mixing pre-setup for vgmstream (doesn't imply usage). + * If init somehow fails next calls are ignored. */ +void mixing_init(VGMSTREAM* vgmstream); +void mixing_close(VGMSTREAM* vgmstream); +void mixing_update_channel(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 + * of down/upmixing without querying input/output_channels). */ +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); + +/* adds mixes filtering and optimizing if needed */ +void mixing_push_swap(VGMSTREAM* vgmstream, int ch_dst, int ch_src); +void mixing_push_add(VGMSTREAM* vgmstream, int ch_dst, int ch_src, double volume); +void mixing_push_volume(VGMSTREAM* vgmstream, int ch_dst, double volume); +void mixing_push_limit(VGMSTREAM* vgmstream, int ch_dst, double volume); +void mixing_push_upmix(VGMSTREAM* vgmstream, int ch_dst); +void mixing_push_downmix(VGMSTREAM* vgmstream, int ch_dst); +void mixing_push_killmix(VGMSTREAM* vgmstream, int ch_dst); +void mixing_push_fade(VGMSTREAM* vgmstream, int ch_dst, double vol_start, double vol_end, char shape, int32_t time_pre, int32_t time_start, int32_t time_end, int32_t time_post); + +void mixing_macro_volume(VGMSTREAM* vgmstream, double volume, uint32_t mask); +void mixing_macro_track(VGMSTREAM* vgmstream, uint32_t mask); +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); + + +#endif /* _MIXING_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/plugins.c b/Frameworks/vgmstream/vgmstream/src/plugins.c new file mode 100644 index 000000000..4baf602f8 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/plugins.c @@ -0,0 +1,245 @@ +#include "vgmstream.h" +#include "plugins.h" +#include "mixing.h" + +#define VGMSTREAM_TAGS_LINE_MAX 2048 + +/* opaque tag state */ +struct VGMSTREAM_TAGS { + /* extracted output */ + char key[VGMSTREAM_TAGS_LINE_MAX]; + char val[VGMSTREAM_TAGS_LINE_MAX]; + + /* file to find tags for */ + char targetname[VGMSTREAM_TAGS_LINE_MAX]; + /* path of targetname */ + char targetpath[VGMSTREAM_TAGS_LINE_MAX]; + + /* tag section for filename (see comments below) */ + int section_found; + off_t section_start; + off_t section_end; + off_t offset; + + /* commands */ + int autotrack_on; + int autotrack_written; + int track_count; + + int autoalbum_on; + int autoalbum_written; +}; + + +static void tags_clean(VGMSTREAM_TAGS* tag) { + int i; + int val_len = strlen(tag->val); + + /* remove trailing spaces */ + for (i = val_len - 1; i > 0; i--) { + if (tag->val[i] != ' ') + break; + tag->val[i] = '\0'; + } +} + +VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val) { + VGMSTREAM_TAGS* tags = malloc(sizeof(VGMSTREAM_TAGS)); + if (!tags) goto fail; + + *tag_key = tags->key; + *tag_val = tags->val; + + return tags; +fail: + return NULL; +} + +void vgmstream_tags_close(VGMSTREAM_TAGS *tags) { + free(tags); +} + +/* Tags are divided in two: "global" @TAGS and "file" %TAGS for target filename. To extract both + * we find the filename's tag "section": (other_filename) ..(#tag section).. (target_filename). + * When a new "other_filename" is found that offset is marked as section_start, and when target_filename + * is found it's marked as section_end. Then we can begin extracting tags within that section, until + * all tags are exhausted. Global tags are extracted while searching, so they always go first, and + * also meaning any tags after the section is found are ignored. */ +int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile) { + off_t file_size = get_streamfile_size(tagfile); + char currentname[VGMSTREAM_TAGS_LINE_MAX] = {0}; + char line[VGMSTREAM_TAGS_LINE_MAX] = {0}; + int ok, bytes_read, line_done; + + if (!tags) + return 0; + + /* prepare file start and skip BOM if needed */ + if (tags->offset == 0) { + if ((uint16_t)read_16bitLE(0x00, tagfile) == 0xFFFE || + (uint16_t)read_16bitLE(0x00, tagfile) == 0xFEFF) { + tags->offset = 0x02; + if (tags->section_start == 0) + tags->section_start = 0x02; + } + else if (((uint32_t)read_32bitBE(0x00, tagfile) & 0xFFFFFF00) == 0xEFBBBF00) { + tags->offset = 0x03; + if (tags->section_start == 0) + tags->section_start = 0x03; + } + } + + /* read lines */ + while (tags->offset <= file_size) { + + /* no more tags to extract */ + if (tags->section_found && tags->offset >= tags->section_end) { + + /* write extra tags after all regular tags */ + if (tags->autotrack_on && !tags->autotrack_written) { + sprintf(tags->key, "%s", "TRACK"); + sprintf(tags->val, "%i", tags->track_count); + tags->autotrack_written = 1; + return 1; + } + + if (tags->autoalbum_on && !tags->autoalbum_written && tags->targetpath[0] != '\0') { + const char* path; + + path = strrchr(tags->targetpath,'\\'); + if (!path) { + path = strrchr(tags->targetpath,'/'); + } + if (!path) { + path = tags->targetpath; + } + + sprintf(tags->key, "%s", "ALBUM"); + sprintf(tags->val, "%s", path+1); + tags->autoalbum_written = 1; + return 1; + } + + goto fail; + } + + bytes_read = get_streamfile_text_line(VGMSTREAM_TAGS_LINE_MAX,line, tags->offset,tagfile, &line_done); + if (!line_done || bytes_read == 0) goto fail; + + tags->offset += bytes_read; + + + if (tags->section_found) { + /* find possible file tag */ + ok = sscanf(line, "# %%%[^ \t] %[^\r\n] ", tags->key,tags->val); + if (ok == 2) { + tags_clean(tags); + return 1; + } + } + else { + + if (line[0] == '#') { + /* find possible global command */ + ok = sscanf(line, "# $%[^ \t] %[^\r\n]", tags->key,tags->val); + if (ok == 1 || ok == 2) { + if (strcasecmp(tags->key,"AUTOTRACK") == 0) { + tags->autotrack_on = 1; + } + else if (strcasecmp(tags->key,"AUTOALBUM") == 0) { + tags->autoalbum_on = 1; + } + + continue; /* not an actual tag */ + } + + /* find possible global tag */ + ok = sscanf(line, "# @%[^ \t] %[^\r\n]", tags->key,tags->val); + if (ok == 2) { + tags_clean(tags); + return 1; + } + + continue; /* next line */ + } + + /* find possible filename and section start/end */ + ok = sscanf(line, " %[^\r\n] ", currentname); + if (ok == 1) { + if (strcasecmp(tags->targetname,currentname) == 0) { /* looks ok even for UTF-8 */ + /* section ok, start would be set before this (or be 0) */ + tags->section_end = tags->offset; + tags->section_found = 1; + tags->offset = tags->section_start; + } + else { + /* mark new possible section */ + tags->section_start = tags->offset; + } + + tags->track_count++; /* new track found (target filename or not) */ + continue; + } + + /* empty/bad line, probably */ + } + } + + /* may reach here if read up to file_size but no section was found */ + +fail: + tags->key[0] = '\0'; + tags->val[0] = '\0'; + return 0; +} + + +void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename) { + char *path; + + if (!tags) + return; + + memset(tags, 0, sizeof(VGMSTREAM_TAGS)); + + //todo validate sizes and copy sensible max + + /* get base name */ + strcpy(tags->targetpath, target_filename); + + /* Windows CMD accepts both \\ and /, and maybe plugin uses either */ + path = strrchr(tags->targetpath,'\\'); + if (!path) { + path = strrchr(tags->targetpath,'/'); + } + if (path != NULL) { + path[0] = '\0'; /* leave targetpath with path only */ + path = path+1; + } + + if (path) { + strcpy(tags->targetname, path); + } else { + tags->targetpath[0] = '\0'; + strcpy(tags->targetname, target_filename); + } +} + +void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels) { + mixing_setup(vgmstream, max_sample_count); + mixing_info(vgmstream, input_channels, output_channels); +} + +void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels) { + if (max_channels <= 0) + return; + + /* guess mixing the best we can */ + //todo: could use standard downmixing for known max_channels <> vgmstream->channels combos: + // https://www.audiokinetic.com/library/edge/?source=Help&id=downmix_tables#tbl_mono + // https://www.audiokinetic.com/library/edge/?source=Help&id=standard_configurations + + mixing_macro_layer(vgmstream, max_channels, 0, 'e'); + + return; +} diff --git a/Frameworks/vgmstream/vgmstream/src/plugins.h b/Frameworks/vgmstream/vgmstream/src/plugins.h new file mode 100644 index 000000000..630214235 --- /dev/null +++ b/Frameworks/vgmstream/vgmstream/src/plugins.h @@ -0,0 +1,95 @@ +/* + * plugins.h - helper for plugins + */ +#ifndef _PLUGINS_H_ +#define _PLUGINS_H_ + +#include "streamfile.h" + + +#if 0 +/* ****************************************** */ +/* PLAYER: simplifies plugin code */ +/* ****************************************** */ + +/* opaque player state */ +typedef struct VGMSTREAM_PLAYER VGMSTREAM_PLAYER; + +typedef struct { + //... +} VGMSTREAM_PLAYER_INFO; + +VGMSTREAM_PLAYER* vgmstream_player_init(...); + +VGMSTREAM_PLAYER* vgmstream_player_format_check(...); +VGMSTREAM_PLAYER* vgmstream_player_set_format_whilelist(...); +VGMSTREAM_PLAYER* vgmstream_player_set_format_blacklist(...); + +VGMSTREAM_PLAYER* vgmstream_player_set_file(...); + +VGMSTREAM_PLAYER* vgmstream_player_get_config(...); + +VGMSTREAM_PLAYER* vgmstream_player_set_config(...); + +VGMSTREAM_PLAYER* vgmstream_player_get_buffer(...); + +VGMSTREAM_PLAYER* vgmstream_player_get_info(...); + +VGMSTREAM_PLAYER* vgmstream_player_describe(...); + +VGMSTREAM_PLAYER* vgmstream_player_get_title(...); + +VGMSTREAM_PLAYER* vgmstream_player_get_tagfile(...); + +VGMSTREAM_PLAYER* vgmstream_player_play(...); + +VGMSTREAM_PLAYER* vgmstream_player_seek(...); + +VGMSTREAM_PLAYER* vgmstream_player_close(...); + +#endif + + + +/* ****************************************** */ +/* TAGS: loads key=val tags from a file */ +/* ****************************************** */ + +/* opaque tag state */ +typedef struct VGMSTREAM_TAGS VGMSTREAM_TAGS; + +/* Initializes TAGS and returns pointers to extracted strings (always valid but change + * on every vgmstream_tags_next_tag call). Next functions are safe to call even if this fails (validate NULL). + * ex.: const char *tag_key, *tag_val; tags=vgmstream_tags_init(&tag_key, &tag_val); */ +VGMSTREAM_TAGS* vgmstream_tags_init(const char* *tag_key, const char* *tag_val); + +/* Resets tagfile to restart reading from the beginning for a new filename. + * Must be called first before extracting tags. */ +void vgmstream_tags_reset(VGMSTREAM_TAGS* tags, const char* target_filename); + + +/* Extracts next valid tag in tagfile to *tag. Returns 0 if no more tags are found (meant to be + * called repeatedly until 0). Key/values are trimmed and values can be in UTF-8. */ +int vgmstream_tags_next_tag(VGMSTREAM_TAGS* tags, STREAMFILE* tagfile); + +/* Closes tag file */ +void vgmstream_tags_close(VGMSTREAM_TAGS* tags); + + +/* ****************************************** */ +/* MIXING: modifies vgmstream output */ +/* ****************************************** */ + +/* Enables mixing effects, with max outbuf samples as a hint. Once active, plugin + * must use returned input_channels to create outbuf and output_channels to output audio. + * max_sample_count may be 0 if you only need to query values and not actually enable it. + * Needs to be enabled last after adding effects. */ +void vgmstream_mixing_enable(VGMSTREAM* vgmstream, int32_t max_sample_count, int *input_channels, int *output_channels); + +/* sets automatic downmixing if vgmstream's channels are higher than max_channels */ +void vgmstream_mixing_autodownmix(VGMSTREAM *vgmstream, int max_channels); + +/* sets a fadeout */ +//void vgmstream_mixing_fadeout(VGMSTREAM *vgmstream, float start_second, float duration_seconds); + +#endif /* _PLUGINS_H_ */ diff --git a/Frameworks/vgmstream/vgmstream/src/streamfile.c b/Frameworks/vgmstream/vgmstream/src/streamfile.c index aa1767902..472726e93 100644 --- a/Frameworks/vgmstream/vgmstream/src/streamfile.c +++ b/Frameworks/vgmstream/vgmstream/src/streamfile.c @@ -1111,30 +1111,41 @@ void get_streamfile_ext(STREAMFILE *streamFile, char * filename, size_t size) { } /* debug util, mainly for custom IO testing */ -void dump_streamfile(STREAMFILE *streamFile, const char* out) { +void dump_streamfile(STREAMFILE *streamFile, int num) { #ifdef VGM_DEBUG_OUTPUT off_t offset = 0; FILE *f = NULL; - if (out) { - f = fopen(out,"wb"); + if (num >= 0) { + char filename[PATH_LIMIT]; + char dumpname[PATH_LIMIT]; + + get_streamfile_filename(streamFile, filename, PATH_LIMIT); + snprintf(dumpname,PATH_LIMIT, "%s_%02i.dump", filename, num); + + f = fopen(dumpname,"wb"); if (!f) return; } - VGM_LOG("dump streamfile, size: %x\n", get_streamfile_size(streamFile)); + VGM_LOG("dump streamfile: size %x\n", get_streamfile_size(streamFile)); while (offset < get_streamfile_size(streamFile)) { uint8_t buffer[0x8000]; size_t read; read = read_streamfile(buffer,offset,0x8000,streamFile); - if (out) + if(!read) { + VGM_LOG("dump streamfile: can't read at %lx\n", offset); + break; + } + + if (f) fwrite(buffer,sizeof(uint8_t),read, f); else VGM_LOGB(buffer,read,0); offset += read; } - if (out) { + if (f) { fclose(f); } #endif diff --git a/Frameworks/vgmstream/vgmstream/src/streamfile.h b/Frameworks/vgmstream/vgmstream/src/streamfile.h index a69da42f7..3e31a60e5 100644 --- a/Frameworks/vgmstream/vgmstream/src/streamfile.h +++ b/Frameworks/vgmstream/vgmstream/src/streamfile.h @@ -61,7 +61,7 @@ typedef struct _STREAMFILE { size_t (*read)(struct _STREAMFILE *,uint8_t * dest, off_t offset, size_t length); size_t (*get_size)(struct _STREAMFILE *); - off_t (*get_offset)(struct _STREAMFILE *); + off_t (*get_offset)(struct _STREAMFILE *); //todo: DO NOT USE, NOT RESET PROPERLY (remove?) /* for dual-file support */ void (*get_name)(struct _STREAMFILE *,char *name,size_t length); struct _STREAMFILE * (*open)(struct _STREAMFILE *,const char * const filename,size_t buffersize); @@ -191,6 +191,50 @@ static inline int8_t read_8bit(off_t offset, STREAMFILE * streamfile) { return buf[0]; } +/* alias of the above */ +static inline int8_t read_s8(off_t offset, STREAMFILE * streamfile) { return read_8bit(offset, streamfile); } +static inline uint8_t read_u8(off_t offset, STREAMFILE * streamfile) { return (uint8_t)read_8bit(offset, streamfile); } +static inline int16_t read_s16le(off_t offset, STREAMFILE * streamfile) { return read_16bitLE(offset, streamfile); } +static inline uint16_t read_u16le(off_t offset, STREAMFILE * streamfile) { return (uint16_t)read_16bitLE(offset, streamfile); } +static inline int16_t read_s16be(off_t offset, STREAMFILE * streamfile) { return read_16bitBE(offset, streamfile); } +static inline uint16_t read_u16be(off_t offset, STREAMFILE * streamfile) { return (uint16_t)read_16bitBE(offset, streamfile); } +static inline int32_t read_s32le(off_t offset, STREAMFILE * streamfile) { return read_32bitLE(offset, streamfile); } +static inline uint32_t read_u32le(off_t offset, STREAMFILE * streamfile) { return (uint32_t)read_32bitLE(offset, streamfile); } +static inline int32_t read_s32be(off_t offset, STREAMFILE * streamfile) { return read_32bitBE(offset, streamfile); } +static inline uint32_t read_u32be(off_t offset, STREAMFILE * streamfile) { return (uint32_t)read_32bitBE(offset, streamfile); } +static inline int64_t read_s64be(off_t offset, STREAMFILE * streamfile) { return read_64bitBE(offset, streamfile); } +static inline uint64_t read_u64be(off_t offset, STREAMFILE * streamfile) { return (uint64_t)read_64bitBE(offset, streamfile); } +static inline int64_t read_s64le(off_t offset, STREAMFILE * streamfile) { return read_64bitLE(offset, streamfile); } +static inline uint64_t read_u64le(off_t offset, STREAMFILE * streamfile) { return (uint64_t)read_64bitLE(offset, streamfile); } + +#if 0 //todo improve + test + simplify code (maybe not inline?) +static inline float read_f32be(off_t offset, STREAMFILE * streamfile) { + uint32_t sample_int = read_s32be(offset,streamfile); + float* sample_float = (float*)&sample_int; + return *sample_float; +} +static inline float read_f32le(off_t offset, STREAMFILE * streamfile) { + ... +} +static inline int read_s4h(off_t offset, STREAMFILE * streamfile) { + uint8_t byte = read_u8(offset, streamfile); + return get_nibble_signed(byte, 1); +} +static inline int read_u4h(off_t offset, STREAMFILE * streamfile) { + uint8_t byte = read_u8(offset, streamfile); + return (byte >> 4) & 0x0f; +} +static inline int read_s4l(off_t offset, STREAMFILE * streamfile) { + ... +} +static inline int read_u4l(off_t offset, STREAMFILE * streamfile) { + ... +} +static inline int max_s32(int32_t a, int32_t b) { return a > b ? a : b; } +static inline int min_s32(int32_t a, int32_t b) { return a < b ? a : b; } +//align32, align16, clamp16, etc +#endif + /* guess byte endianness from a given value, return true if big endian and false if little endian */ static inline int guess_endianness16bit(off_t offset, STREAMFILE * streamfile) { uint8_t buf[0x02]; @@ -231,5 +275,5 @@ void get_streamfile_basename(STREAMFILE *streamFile, char * buffer, size_t size) void get_streamfile_path(STREAMFILE *streamFile, char * buffer, size_t size); void get_streamfile_ext(STREAMFILE *streamFile, char * filename, size_t size); -void dump_streamfile(STREAMFILE *streamFile, const char* out); +void dump_streamfile(STREAMFILE *streamFile, int num); #endif diff --git a/Frameworks/vgmstream/vgmstream/src/streamtypes.h b/Frameworks/vgmstream/vgmstream/src/streamtypes.h index c4be0d310..35c776254 100644 --- a/Frameworks/vgmstream/vgmstream/src/streamtypes.h +++ b/Frameworks/vgmstream/vgmstream/src/streamtypes.h @@ -29,6 +29,7 @@ #include #endif /* _MSC_VER */ -typedef int16_t sample; +typedef int16_t sample; //TODO: deprecated, remove +typedef int16_t sample_t; #endif diff --git a/Frameworks/vgmstream/vgmstream/src/util.c b/Frameworks/vgmstream/vgmstream/src/util.c index 2895c62a8..bda83e8b4 100644 --- a/Frameworks/vgmstream/vgmstream/src/util.c +++ b/Frameworks/vgmstream/vgmstream/src/util.c @@ -6,29 +6,33 @@ const char * filename_extension(const char * pathname) { const char * filename; const char * extension; - /* get basename + extension */ - filename = pathname; -#if 0 - //must detect empty extensions in folders with . in the name; not too important and DIR_SEPARATOR could improved - filename = strrchr(pathname, DIR_SEPARATOR); - if (filename == NULL) - filename = pathname; /* pathname has no separators (single filename) */ - else - filename++; /* skip the separator */ -#endif + /* favor strrchr (optimized/aligned) rather than homemade loops */ + + /* find possible separator first to avoid misdetecting folders with dots + extensionless files + * (allow both slashes as plugin could pass normalized '/') */ + filename = strrchr(pathname, '/'); + if (filename != NULL) + filename++; /* skip separator */ + else { + filename = strrchr(pathname, '\\'); + if (filename != NULL) + filename++; /* skip separator */ + else + filename = pathname; /* pathname has no separators (single filename) */ + } extension = strrchr(filename,'.'); - if (extension==NULL) - extension = filename+strlen(filename); /* point to null, i.e. an empty string for the extension */ + if (extension != NULL) + extension++; /* skip dot */ else - extension++; /* skip the dot */ + extension = filename + strlen(filename); /* point to null (empty "" string for extensionless files) */ return extension; } /* unused */ /* -void interleave_channel(sample * outbuffer, sample * inbuffer, int32_t sample_count, int channel_count, int channel_number) { +void interleave_channel(sample_t * outbuffer, sample_t * inbuffer, int32_t sample_count, int channel_count, int channel_number) { int32_t insample,outsample; if (channel_count==1) { @@ -44,9 +48,9 @@ void interleave_channel(sample * outbuffer, sample * inbuffer, int32_t sample_co /* failed attempt at interleave in place */ /* -void interleave_stereo(sample * buffer, int32_t sample_count) { +void interleave_stereo(sample_t * buffer, int32_t sample_count) { int32_t tomove, belongs; - sample moving,temp; + sample_t moving,temp; tomove = sample_count; moving = buffer[tomove]; @@ -96,11 +100,11 @@ void put_32bitBE(uint8_t * buf, int32_t i) { buf[3] = (uint8_t)(i & 0xFF); } -void swap_samples_le(sample *buf, int count) { +void swap_samples_le(sample_t *buf, int count) { int i; - for (i=0;i>8; + for (i = 0; i < count; i++) { + uint8_t b0 = buf[i] & 0xff; + uint8_t b1 = buf[i] >> 8; uint8_t *p = (uint8_t*)&(buf[i]); p[0] = b0; p[1] = b1; diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.c b/Frameworks/vgmstream/vgmstream/src/vgmstream.c index cbc643c4f..1cd0be539 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.c +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.c @@ -9,11 +9,12 @@ #include "meta/meta.h" #include "layout/layout.h" #include "coding/coding.h" +#include "mixing.h" static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *streamFile, VGMSTREAM* (*init_vgmstream_function)(STREAMFILE*)); -/* List of functions that will recognize files */ +/* list of metadata parser functions that will recognize files, used on init */ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_adx, init_vgmstream_brstm, @@ -37,7 +38,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_cstr, init_vgmstream_gcsw, init_vgmstream_ps2_ads, - init_vgmstream_ps2_npsf, + init_vgmstream_nps, init_vgmstream_rwsd, init_vgmstream_xa, init_vgmstream_ps2_rxws, @@ -104,7 +105,9 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_musx_v201, init_vgmstream_leg, init_vgmstream_filp, - init_vgmstream_ikm, + init_vgmstream_ikm_ps2, + init_vgmstream_ikm_pc, + init_vgmstream_ikm_psp, init_vgmstream_sfs, init_vgmstream_bg00, init_vgmstream_sat_dvi, @@ -191,7 +194,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ngc_ssm, init_vgmstream_ps2_joe, init_vgmstream_vgs, - init_vgmstream_dc_dcsw_dcs, + init_vgmstream_dcs_wav, init_vgmstream_mul, init_vgmstream_thp, init_vgmstream_wii_sts, @@ -248,7 +251,6 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ps2_ster, init_vgmstream_ps2_wb, init_vgmstream_bnsf, - init_vgmstream_s14_sss, init_vgmstream_ps2_gcm, init_vgmstream_ps2_smpl, init_vgmstream_ps2_msa, @@ -279,7 +281,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ngc_nst_dsp, init_vgmstream_baf, init_vgmstream_baf_badrip, - init_vgmstream_ps3_msf, + init_vgmstream_msf, init_vgmstream_nub_vag, init_vgmstream_ps3_past, init_vgmstream_sgxd, @@ -298,7 +300,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_pc_adp_otns, init_vgmstream_eb_sfx, init_vgmstream_eb_sf0, - init_vgmstream_ps2_mtaf, + init_vgmstream_mtaf, init_vgmstream_tun, init_vgmstream_wpd, init_vgmstream_mn_str, @@ -344,7 +346,8 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ta_aac_mobile_vorbis, init_vgmstream_ta_aac_vita, init_vgmstream_va3, - init_vgmstream_ps3_mta2, + init_vgmstream_mta2, + init_vgmstream_mta2_container, init_vgmstream_ngc_ulw, init_vgmstream_pc_xa30, init_vgmstream_wii_04sw, @@ -371,6 +374,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_naac, init_vgmstream_ubi_sb, init_vgmstream_ubi_sm, + init_vgmstream_ubi_bnm, init_vgmstream_ezw, init_vgmstream_vxn, init_vgmstream_ea_snr_sns, @@ -378,6 +382,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_ea_abk_eaac, init_vgmstream_ea_hdr_sth_dat, init_vgmstream_ea_mpf_mus_eaac, + init_vgmstream_ea_tmx, init_vgmstream_ea_sbr, init_vgmstream_ea_sbr_harmony, init_vgmstream_ngc_vid1, @@ -465,12 +470,23 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = { init_vgmstream_gin, init_vgmstream_dsf, init_vgmstream_208, + init_vgmstream_dsp_ds2, + init_vgmstream_ffdl, + init_vgmstream_mus_vc, + init_vgmstream_strm_abylight, + init_vgmstream_sfh, + init_vgmstream_ea_schl_video, + init_vgmstream_msf_konami, + init_vgmstream_xwma_konami, + init_vgmstream_9tav, + init_vgmstream_fsb5_fev_bank, /* lowest priority metas (should go after all metas, and TXTH should go before raw formats) */ init_vgmstream_txth, /* proper parsers should supersede TXTH, once added */ init_vgmstream_ps2_int, /* .int raw PS-ADPCM */ init_vgmstream_ps_headerless, /* tries to detect a bunch of PS-ADPCM formats */ init_vgmstream_pc_snds, /* .snds PC, after ps_headerless */ + init_vgmstream_s14_sss, /* .raw siren14 */ init_vgmstream_raw, /* .raw PCM */ #ifdef VGM_USE_FFMPEG init_vgmstream_ffmpeg, /* may play anything incorrectly, since FFmpeg doesn't check extensions */ @@ -487,7 +503,7 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { fcns_size = (sizeof(init_vgmstream_functions)/sizeof(init_vgmstream_functions[0])); /* try a series of formats, see which works */ - for (i=0; i < fcns_size; i++) { + for (i =0; i < fcns_size; i++) { /* call init function and see if valid VGMSTREAM was returned */ VGMSTREAM * vgmstream = (init_vgmstream_functions[i])(streamFile); if (!vgmstream) @@ -495,25 +511,28 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { /* fail if there is nothing to play (without this check vgmstream can generate empty files) */ if (vgmstream->num_samples <= 0) { - VGM_LOG("VGMSTREAM: wrong num_samples (ns=%i / 0x%08x)\n", vgmstream->num_samples, vgmstream->num_samples); + VGM_LOG("VGMSTREAM: wrong num_samples %i\n", vgmstream->num_samples); close_vgmstream(vgmstream); continue; } - /* everything should have a reasonable sample rate (300 is Wwise min) */ - if (vgmstream->sample_rate < 300 || vgmstream->sample_rate > 96000) { - VGM_LOG("VGMSTREAM: wrong sample rate (sr=%i)\n", vgmstream->sample_rate); + /* everything should have a reasonable sample rate */ + if (vgmstream->sample_rate < VGMSTREAM_MIN_SAMPLE_RATE || vgmstream->sample_rate > VGMSTREAM_MAX_SAMPLE_RATE) { + VGM_LOG("VGMSTREAM: wrong sample_rate %i\n", vgmstream->sample_rate); close_vgmstream(vgmstream); continue; } - - /* Sanify loops! */ + + /* sanify loops and remove bad metadata */ if (vgmstream->loop_flag) { - if ((vgmstream->loop_end_sample <= vgmstream->loop_start_sample) - || (vgmstream->loop_end_sample > vgmstream->num_samples) - || (vgmstream->loop_start_sample < 0) ) { + if (vgmstream->loop_end_sample <= vgmstream->loop_start_sample + || vgmstream->loop_end_sample > vgmstream->num_samples + || vgmstream->loop_start_sample < 0) { + VGM_LOG("VGMSTREAM: wrong loops ignored (lss=%i, lse=%i, ns=%i)\n", + vgmstream->loop_start_sample, vgmstream->loop_end_sample, vgmstream->num_samples); vgmstream->loop_flag = 0; - VGM_LOG("VGMSTREAM: wrong loops ignored (lss=%i, lse=%i, ns=%i)\n", vgmstream->loop_start_sample, vgmstream->loop_end_sample, vgmstream->num_samples); + vgmstream->loop_start_sample = 0; + vgmstream->loop_end_sample = 0; } } @@ -522,6 +541,12 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { try_dual_file_stereo(vgmstream, streamFile, init_vgmstream_functions[i]); } + /* clean as loops are readable metadata but loop fields may contain garbage + * (done *after* dual stereo as it needs loop fields to match) */ + if (!vgmstream->loop_flag) { + vgmstream->loop_start_sample = 0; + vgmstream->loop_end_sample = 0; + } #ifdef VGM_USE_FFMPEG /* check FFmpeg streams here, for lack of a better place */ @@ -533,22 +558,41 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { } #endif + /* some players are picky with incorrect channel layouts */ + if (vgmstream->channel_layout > 0) { + int output_channels = vgmstream->channels; + int ch, count = 0, max_ch = 32; + for (ch = 0; ch < max_ch; ch++) { + int bit = (vgmstream->channel_layout >> ch) & 1; + if (ch > 17 && bit) { + VGM_LOG("VGMSTREAM: wrong bit %i in channel_layout %x\n", ch, vgmstream->channel_layout); + vgmstream->channel_layout = 0; + break; + } + count += bit; + } + + if (count > output_channels) { + VGM_LOG("VGMSTREAM: wrong totals %i in channel_layout %x\n", count, vgmstream->channel_layout); + vgmstream->channel_layout = 0; + } + } + /* files can have thousands subsongs, but let's put a limit */ - if (vgmstream->num_streams < 0 || vgmstream->num_streams > 65535) { + if (vgmstream->num_streams < 0 || vgmstream->num_streams > VGMSTREAM_MAX_SUBSONGS) { VGM_LOG("VGMSTREAM: wrong num_streams (ns=%i)\n", vgmstream->num_streams); close_vgmstream(vgmstream); continue; } - /* save info */ /* stream_index 0 may be used by plugins to signal "vgmstream default" (IOW don't force to 1) */ - if (!vgmstream->stream_index) + if (vgmstream->stream_index == 0) { vgmstream->stream_index = streamFile->stream_index; + } - /* save start things so we can restart for seeking */ - memcpy(vgmstream->start_ch,vgmstream->ch,sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); - memcpy(vgmstream->start_vgmstream,vgmstream,sizeof(VGMSTREAM)); + + setup_vgmstream(vgmstream); /* final setup */ return vgmstream; } @@ -557,6 +601,17 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) { return NULL; } +void setup_vgmstream(VGMSTREAM * vgmstream) { + + /* save start things so we can restart when seeking */ + memcpy(vgmstream->start_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); + memcpy(vgmstream->start_vgmstream, vgmstream, sizeof(VGMSTREAM)); + + /* layout's sub-VGMSTREAM are expected to setup externally and maybe call this, + * as they can be created using init_vgmstream or manually */ +} + + /* format detection and VGMSTREAM setup, uses default parameters */ VGMSTREAM * init_vgmstream(const char * const filename) { VGMSTREAM *vgmstream = NULL; @@ -572,233 +627,221 @@ VGMSTREAM * init_vgmstream_from_STREAMFILE(STREAMFILE *streamFile) { return init_vgmstream_internal(streamFile); } -/* Reset a VGMSTREAM to its state at the start of playback - * (when a plugin needs to seek back to zero, for instance). - * Note that this does not reset the constituent STREAMFILES. */ +/* Reset a VGMSTREAM to its state at the start of playback (when a plugin seeks back to zero). */ void reset_vgmstream(VGMSTREAM * vgmstream) { - /* copy the vgmstream back into itself */ - memcpy(vgmstream,vgmstream->start_vgmstream,sizeof(VGMSTREAM)); - /* copy the initial channels */ - memcpy(vgmstream->ch,vgmstream->start_ch,sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); - - /* loop_ch is not zeroed here because there is a possibility of the + /* reset the VGMSTREAM and channels back to their original state */ + memcpy(vgmstream, vgmstream->start_vgmstream, sizeof(VGMSTREAM)); + memcpy(vgmstream->ch, vgmstream->start_ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); + /* loop_ch is not reset here because there is a possibility of the * init_vgmstream_* function doing something tricky and precomputing it. * Otherwise hit_loop will be 0 and it will be copied over anyway when we * really hit the loop start. */ + /* reset custom codec */ #ifdef VGM_USE_VORBIS - if (vgmstream->coding_type==coding_OGG_VORBIS) { + if (vgmstream->coding_type == coding_OGG_VORBIS) { reset_ogg_vorbis(vgmstream); } - if (vgmstream->coding_type==coding_VORBIS_custom) { + if (vgmstream->coding_type == coding_VORBIS_custom) { reset_vorbis_custom(vgmstream); } #endif - if (vgmstream->coding_type==coding_CRI_HCA) { + if (vgmstream->coding_type == coding_CRI_HCA) { reset_hca(vgmstream->codec_data); } - if (vgmstream->coding_type==coding_EA_MT) { + if (vgmstream->coding_type == coding_EA_MT) { reset_ea_mt(vgmstream); } #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) - if (vgmstream->coding_type==coding_MP4_AAC) { + if (vgmstream->coding_type == coding_MP4_AAC) { reset_mp4_aac(vgmstream); } #endif #ifdef VGM_USE_MPEG - if (vgmstream->coding_type==coding_MPEG_custom || - vgmstream->coding_type==coding_MPEG_ealayer3 || - vgmstream->coding_type==coding_MPEG_layer1 || - vgmstream->coding_type==coding_MPEG_layer2 || - vgmstream->coding_type==coding_MPEG_layer3) { + if (vgmstream->coding_type == coding_MPEG_custom || + vgmstream->coding_type == coding_MPEG_ealayer3 || + vgmstream->coding_type == coding_MPEG_layer1 || + vgmstream->coding_type == coding_MPEG_layer2 || + vgmstream->coding_type == coding_MPEG_layer3) { reset_mpeg(vgmstream); } #endif #ifdef VGM_USE_G7221 - if (vgmstream->coding_type==coding_G7221C) { + if (vgmstream->coding_type == coding_G7221C) { reset_g7221(vgmstream); } #endif #ifdef VGM_USE_G719 - if (vgmstream->coding_type==coding_G719) { + if (vgmstream->coding_type == coding_G719) { reset_g719(vgmstream->codec_data, vgmstream->channels); } #endif #ifdef VGM_USE_MAIATRAC3PLUS - if (vgmstream->coding_type==coding_AT3plus) { + if (vgmstream->coding_type == coding_AT3plus) { reset_at3plus(vgmstream); } #endif #ifdef VGM_USE_ATRAC9 - if (vgmstream->coding_type==coding_ATRAC9) { + if (vgmstream->coding_type == coding_ATRAC9) { reset_atrac9(vgmstream); } #endif #ifdef VGM_USE_CELT - if (vgmstream->coding_type==coding_CELT_FSB) { + if (vgmstream->coding_type == coding_CELT_FSB) { reset_celt_fsb(vgmstream); } #endif #ifdef VGM_USE_FFMPEG - if (vgmstream->coding_type==coding_FFmpeg) { + if (vgmstream->coding_type == coding_FFmpeg) { reset_ffmpeg(vgmstream); } #endif - if (vgmstream->coding_type==coding_ACM) { + if (vgmstream->coding_type == coding_ACM) { reset_acm(vgmstream->codec_data); } if (vgmstream->coding_type == coding_NWA) { nwa_codec_data *data = vgmstream->codec_data; - if (data) - reset_nwa(data->nwa); + if (data) reset_nwa(data->nwa); } - - if (vgmstream->layout_type==layout_aix) { - aix_codec_data *data = vgmstream->codec_data; - int i; - - data->current_segment = 0; - for (i=0;isegment_count*data->stream_count;i++) { - reset_vgmstream(data->adxs[i]); - } - } - - if (vgmstream->layout_type==layout_segmented) { + /* reset custom layouts */ + if (vgmstream->layout_type == layout_segmented) { reset_layout_segmented(vgmstream->layout_data); } - if (vgmstream->layout_type==layout_layered) { + if (vgmstream->layout_type == layout_layered) { reset_layout_layered(vgmstream->layout_data); } + + /* note that this does not reset the constituent STREAMFILES + * (vgmstream->ch[N].streamfiles' internal state, though shouldn't matter) */ } /* Allocate memory and setup a VGMSTREAM */ -VGMSTREAM * allocate_vgmstream(int channel_count, int looped) { +VGMSTREAM * allocate_vgmstream(int channel_count, int loop_flag) { VGMSTREAM * vgmstream; - VGMSTREAM * start_vgmstream; - VGMSTREAMCHANNEL * channels; - VGMSTREAMCHANNEL * start_channels; - VGMSTREAMCHANNEL * loop_channels; - /* up to ~16 aren't too rare for multilayered files, more is probably a bug */ - if (channel_count <= 0 || channel_count > 64) { + /* up to ~16-24 aren't too rare for multilayered files, more is probably a bug */ + if (channel_count <= 0 || channel_count > VGMSTREAM_MAX_CHANNELS) { VGM_LOG("VGMSTREAM: error allocating %i channels\n", channel_count); return NULL; } + /* VGMSTREAM's alloc'ed internals work as follows: + * - vgmstream: main struct (config+state) modified by metas, layouts and codings as needed + * - ch: config+state per channel, also modified by those + * - start_vgmstream: vgmstream clone copied on init_vgmstream and restored on reset_vgmstream + * - start_ch: ch clone copied on init_vgmstream and restored on reset_vgmstream + * - loop_ch: ch clone copied on loop start and restored on loop end (vgmstream_do_loop) + * - codec/layout_data: custom state for complex codecs or layouts, handled externally + * + * Here we only create the basic structs to be filled, and only after init_vgmstream it + * can be considered ready. Clones are shallow copies, in that they share alloc'ed struts + * (like, vgmstream->ch and start_vgmstream->ch will be the same after init_vgmstream, or + * start_vgmstream->start_vgmstream will end up pointing to itself) + * + * This is all a bit too brittle, so code alloc'ing or changing anything sensitive should + * take care clones are properly synced. + */ + + /* create vgmstream + main structs (other data is 0'ed) */ vgmstream = calloc(1,sizeof(VGMSTREAM)); if (!vgmstream) return NULL; - vgmstream->ch = NULL; - vgmstream->start_ch = NULL; - vgmstream->loop_ch = NULL; - vgmstream->start_vgmstream = NULL; - vgmstream->codec_data = NULL; + vgmstream->start_vgmstream = calloc(1,sizeof(VGMSTREAM)); + if (!vgmstream->start_vgmstream) goto fail; - start_vgmstream = calloc(1,sizeof(VGMSTREAM)); - if (!start_vgmstream) { - free(vgmstream); - return NULL; - } - vgmstream->start_vgmstream = start_vgmstream; - start_vgmstream->start_vgmstream = start_vgmstream; + vgmstream->ch = calloc(channel_count,sizeof(VGMSTREAMCHANNEL)); + if (!vgmstream->ch) goto fail; - channels = calloc(channel_count,sizeof(VGMSTREAMCHANNEL)); - if (!channels) { - free(vgmstream); - free(start_vgmstream); - return NULL; + vgmstream->start_ch = calloc(channel_count,sizeof(VGMSTREAMCHANNEL)); + if (!vgmstream->start_ch) goto fail; + + if (loop_flag) { + vgmstream->loop_ch = calloc(channel_count,sizeof(VGMSTREAMCHANNEL)); + if (!vgmstream->loop_ch) goto fail; } - vgmstream->ch = channels; + vgmstream->channels = channel_count; + vgmstream->loop_flag = loop_flag; - start_channels = calloc(channel_count,sizeof(VGMSTREAMCHANNEL)); - if (!start_channels) { - free(vgmstream); - free(start_vgmstream); - free(channels); - return NULL; - } - vgmstream->start_ch = start_channels; - - if (looped) { - loop_channels = calloc(channel_count,sizeof(VGMSTREAMCHANNEL)); - if (!loop_channels) { - free(vgmstream); - free(start_vgmstream); - free(channels); - free(start_channels); - return NULL; - } - vgmstream->loop_ch = loop_channels; - } - - vgmstream->loop_flag = looped; + mixing_init(vgmstream); /* pre-init */ + //vgmstream->stream_name_size = STREAM_NAME_SIZE; return vgmstream; +fail: + if (vgmstream) { + mixing_close(vgmstream); + free(vgmstream->ch); + free(vgmstream->start_ch); + free(vgmstream->loop_ch); + free(vgmstream->start_vgmstream); + } + free(vgmstream); + return NULL; } void close_vgmstream(VGMSTREAM * vgmstream) { if (!vgmstream) return; + /* free custom codecs */ #ifdef VGM_USE_VORBIS - if (vgmstream->coding_type==coding_OGG_VORBIS) { + if (vgmstream->coding_type == coding_OGG_VORBIS) { free_ogg_vorbis(vgmstream->codec_data); vgmstream->codec_data = NULL; } - if (vgmstream->coding_type==coding_VORBIS_custom) { + if (vgmstream->coding_type == coding_VORBIS_custom) { free_vorbis_custom(vgmstream->codec_data); vgmstream->codec_data = NULL; } #endif - if (vgmstream->coding_type==coding_CRI_HCA) { + if (vgmstream->coding_type == coding_CRI_HCA) { free_hca(vgmstream->codec_data); vgmstream->codec_data = NULL; } - if (vgmstream->coding_type==coding_EA_MT) { + if (vgmstream->coding_type == coding_EA_MT) { free_ea_mt(vgmstream->codec_data, vgmstream->channels); vgmstream->codec_data = NULL; } #ifdef VGM_USE_FFMPEG - if (vgmstream->coding_type==coding_FFmpeg) { + if (vgmstream->coding_type == coding_FFmpeg) { free_ffmpeg(vgmstream->codec_data); vgmstream->codec_data = NULL; } #endif #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) - if (vgmstream->coding_type==coding_MP4_AAC) { + if (vgmstream->coding_type == coding_MP4_AAC) { free_mp4_aac(vgmstream->codec_data); vgmstream->codec_data = NULL; } #endif #ifdef VGM_USE_MPEG - if (vgmstream->coding_type==coding_MPEG_custom || - vgmstream->coding_type==coding_MPEG_ealayer3 || - vgmstream->coding_type==coding_MPEG_layer1 || - vgmstream->coding_type==coding_MPEG_layer2 || - vgmstream->coding_type==coding_MPEG_layer3) { + if (vgmstream->coding_type == coding_MPEG_custom || + vgmstream->coding_type == coding_MPEG_ealayer3 || + vgmstream->coding_type == coding_MPEG_layer1 || + vgmstream->coding_type == coding_MPEG_layer2 || + vgmstream->coding_type == coding_MPEG_layer3) { free_mpeg(vgmstream->codec_data); vgmstream->codec_data = NULL; } @@ -839,7 +882,7 @@ void close_vgmstream(VGMSTREAM * vgmstream) { } #endif - if (vgmstream->coding_type==coding_ACM) { + if (vgmstream->coding_type == coding_ACM) { free_acm(vgmstream->codec_data); vgmstream->codec_data = NULL; } @@ -855,34 +898,13 @@ void close_vgmstream(VGMSTREAM * vgmstream) { } - if (vgmstream->layout_type==layout_aix) { - aix_codec_data *data = (aix_codec_data *) vgmstream->codec_data; - - if (data) { - if (data->adxs) { - int i; - for (i=0;isegment_count*data->stream_count;i++) { - /* note that the close_streamfile won't do anything but deallocate itself, - * there is only one open file in vgmstream->ch[0].streamfile */ - close_vgmstream(data->adxs[i]); - } - free(data->adxs); - } - if (data->sample_counts) { - free(data->sample_counts); - } - - free(data); - } - vgmstream->codec_data = NULL; - } - - if (vgmstream->layout_type==layout_segmented) { + /* free custom layouts */ + if (vgmstream->layout_type == layout_segmented) { free_layout_segmented(vgmstream->layout_data); vgmstream->layout_data = NULL; } - if (vgmstream->layout_type==layout_layered) { + if (vgmstream->layout_type == layout_layered) { free_layout_layered(vgmstream->layout_data); vgmstream->layout_data = NULL; } @@ -892,15 +914,13 @@ void close_vgmstream(VGMSTREAM * vgmstream) { { int i,j; - for (i=0;ichannels;i++) { + for (i = 0; i < vgmstream->channels; i++) { if (vgmstream->ch[i].streamfile) { close_streamfile(vgmstream->ch[i].streamfile); /* Multiple channels might have the same streamfile. Find the others - * that are the same as this and clear them so they won't be closed - * again. */ - for (j=0;jchannels;j++) { - if (i!=j && vgmstream->ch[j].streamfile == - vgmstream->ch[i].streamfile) { + * that are the same as this and clear them so they won't be closed again. */ + for (j = 0; j < vgmstream->channels; j++) { + if (i != j && vgmstream->ch[j].streamfile == vgmstream->ch[i].streamfile) { vgmstream->ch[j].streamfile = NULL; } } @@ -909,12 +929,11 @@ void close_vgmstream(VGMSTREAM * vgmstream) { } } - if (vgmstream->loop_ch) free(vgmstream->loop_ch); - if (vgmstream->start_ch) free(vgmstream->start_ch); - if (vgmstream->ch) free(vgmstream->ch); - /* the start_vgmstream is considered just data */ - if (vgmstream->start_vgmstream) free(vgmstream->start_vgmstream); - + mixing_close(vgmstream); + free(vgmstream->ch); + free(vgmstream->start_ch); + free(vgmstream->loop_ch); + free(vgmstream->start_vgmstream); free(vgmstream); } @@ -947,11 +966,10 @@ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sa /* this requires a bit more messing with the VGMSTREAM than I'm comfortable with... */ if (loop_flag && !vgmstream->loop_flag && !vgmstream->loop_ch) { vgmstream->loop_ch = calloc(vgmstream->channels,sizeof(VGMSTREAMCHANNEL)); - /* loop_ch will be populated when decoded samples reach loop start */ + if (!vgmstream->loop_ch) loop_flag = 0; /* ??? */ } else if (!loop_flag && vgmstream->loop_flag) { - /* not important though */ - free(vgmstream->loop_ch); + free(vgmstream->loop_ch); /* not important though */ vgmstream->loop_ch = NULL; } @@ -959,10 +977,13 @@ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sa if (loop_flag) { vgmstream->loop_start_sample = loop_start_sample; vgmstream->loop_end_sample = loop_end_sample; - } else { + } +#if 0 /* keep metadata as it's may be shown (with 'loop disabled' info) */ + else { vgmstream->loop_start_sample = 0; vgmstream->loop_end_sample = 0; } +#endif /* propagate changes to layouts that need them */ if (vgmstream->layout_type == layout_layered) { @@ -970,9 +991,14 @@ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sa layered_layout_data *data = vgmstream->layout_data; for (i = 0; i < data->layer_count; i++) { vgmstream_force_loop(data->layers[i], loop_flag, loop_start_sample, loop_end_sample); + /* layer's force_loop also calls setup_vgmstream, no need to do it here */ } } - /* segmented layout only works (ATM) with exact/header loop, full loop or no loop */ + + /* segmented layout loops with standard loop start/end values and works ok */ + + /* notify of new initial state */ + setup_vgmstream(vgmstream); } void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target) { @@ -988,6 +1014,9 @@ void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target) { vgmstream_set_loop_target(data->layers[i], loop_target); } } + + /* notify of new initial state */ + setup_vgmstream(vgmstream); } @@ -1040,9 +1069,6 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre case layout_blocked_vs_square: render_vgmstream_blocked(buffer,sample_count,vgmstream); break; - case layout_aix: - render_vgmstream_aix(buffer,sample_count,vgmstream); - break; case layout_segmented: render_vgmstream_segmented(buffer,sample_count,vgmstream); break; @@ -1053,39 +1079,7 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre break; } - - /* swap channels if set, to create custom channel mappings */ - if (vgmstream->channel_mappings_on) { - int ch_from,ch_to,s; - sample temp; - for (s = 0; s < sample_count; s++) { - for (ch_from = 0; ch_from < vgmstream->channels; ch_from++) { - if (ch_from > 32) - continue; - - ch_to = vgmstream->channel_mappings[ch_from]; - if (ch_to < 1 || ch_to > 32 || ch_to > vgmstream->channels-1 || ch_from == ch_to) - continue; - - temp = buffer[s*vgmstream->channels + ch_from]; - buffer[s*vgmstream->channels + ch_from] = buffer[s*vgmstream->channels + ch_to]; - buffer[s*vgmstream->channels + ch_to] = temp; - } - } - } - - /* channel bitmask to silence non-set channels (up to 32) - * can be used for 'crossfading subsongs' or layered channels, where a set of channels make a song section */ - if (vgmstream->channel_mask) { - int ch,s; - for (s = 0; s < sample_count; s++) { - for (ch = 0; ch < vgmstream->channels; ch++) { - if ((vgmstream->channel_mask >> ch) & 1) - continue; - buffer[s*vgmstream->channels + ch] = 0; - } - } - } + mix_vgmstream(buffer, sample_count, vgmstream); } /* Get the number of samples of a single frame (smallest self-contained sample group, 1/N channels) */ @@ -1149,12 +1143,15 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { case coding_UBI_IMA: case coding_OKI16: return 1; + case coding_PCM4: + case coding_PCM4_U: case coding_IMA_int: case coding_DVI_IMA_int: case coding_3DS_IMA: case coding_WV6_IMA: case coding_ALP_IMA: case coding_FFTA2_IMA: + case coding_BLITZ_IMA: case coding_PCFX: return 2; case coding_XBOX_IMA: @@ -1206,13 +1203,13 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) { return (vgmstream->interleave_block_size - 0x07)*2 + 2; case coding_WS: /* only works if output sample size is 8 bit, which always is for WS ADPCM */ return vgmstream->ws_output_size; - case coding_AICA: - return 1; - case coding_AICA_int: - return 2; case coding_YAMAHA: + return 1; + case coding_YAMAHA_int: + return 2; + case coding_ASKA: return (0x40-0x04*vgmstream->channels) * 2 / vgmstream->channels; - case coding_YAMAHA_NXAP: + case coding_NXAP: return (0x40-0x04) * 2; case coding_NDS_PROCYON: return 30; @@ -1325,6 +1322,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { case coding_CIRCUS_ADPCM: return 0x01; + case coding_PCM4: + case coding_PCM4_U: case coding_IMA: case coding_IMA_int: case coding_DVI_IMA: @@ -1333,6 +1332,7 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { case coding_WV6_IMA: case coding_ALP_IMA: case coding_FFTA2_IMA: + case coding_BLITZ_IMA: case coding_PCFX: case coding_OKI16: return 0x01; @@ -1393,11 +1393,11 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) { return vgmstream->interleave_block_size; case coding_WS: return vgmstream->current_block_size; - case coding_AICA: - case coding_AICA_int: - return 0x01; case coding_YAMAHA: - case coding_YAMAHA_NXAP: + case coding_YAMAHA_int: + return 0x01; + case coding_ASKA: + case coding_NXAP: return 0x40; case coding_NDS_PROCYON: return 0x10; @@ -1467,7 +1467,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. */ -void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to_do, sample * buffer) { +void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to_do, sample_t * buffer) { int ch; switch (vgmstream->coding_type) { @@ -1837,6 +1837,12 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to vgmstream->channels,vgmstream->samples_into_block,samples_to_do); } break; + case coding_BLITZ_IMA: + for (ch = 0; ch < vgmstream->channels; ch++) { + decode_blitz_ima(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, + vgmstream->channels,vgmstream->samples_into_block,samples_to_do); + } + break; case coding_APPLE_IMA4: for (ch = 0; ch < vgmstream->channels; ch++) { @@ -1976,25 +1982,25 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to vgmstream->channels,vgmstream->samples_into_block, samples_to_do, ch); } break; - case coding_AICA: - case coding_AICA_int: + case coding_YAMAHA: + case coding_YAMAHA_int: for (ch = 0; ch < vgmstream->channels; ch++) { - int is_stereo = (vgmstream->channels > 1 && vgmstream->coding_type == coding_AICA); + int is_stereo = (vgmstream->channels > 1 && vgmstream->coding_type == coding_YAMAHA); - decode_aica(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, + decode_yamaha(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, vgmstream->channels,vgmstream->samples_into_block,samples_to_do, ch, is_stereo); } break; - case coding_YAMAHA: + case coding_ASKA: for (ch = 0; ch < vgmstream->channels; ch++) { - decode_yamaha(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, + decode_aska(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, vgmstream->channels,vgmstream->samples_into_block,samples_to_do, ch); } break; - case coding_YAMAHA_NXAP: + case coding_NXAP: for (ch = 0; ch < vgmstream->channels; ch++) { - decode_yamaha_nxap(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, + decode_nxap(&vgmstream->ch[ch],buffer+samples_written*vgmstream->channels+ch, vgmstream->channels,vgmstream->samples_into_block,samples_to_do); } break; @@ -2102,22 +2108,22 @@ int vgmstream_samples_to_do(int samples_this_block, int samples_per_frame, VGMST /* Why did I think this would be any simpler? */ if (vgmstream->loop_flag) { /* are we going to hit the loop end during this block? */ - if (vgmstream->current_sample+samples_left_this_block > vgmstream->loop_end_sample) { + if (vgmstream->current_sample + samples_left_this_block > vgmstream->loop_end_sample) { /* only do to just before it */ - samples_to_do = vgmstream->loop_end_sample-vgmstream->current_sample; + samples_to_do = vgmstream->loop_end_sample - vgmstream->current_sample; } /* are we going to hit the loop start during this block? */ - if (!vgmstream->hit_loop && vgmstream->current_sample+samples_left_this_block > vgmstream->loop_start_sample) { + if (!vgmstream->hit_loop && vgmstream->current_sample + samples_left_this_block > vgmstream->loop_start_sample) { /* only do to just before it */ - samples_to_do = vgmstream->loop_start_sample-vgmstream->current_sample; + samples_to_do = vgmstream->loop_start_sample - vgmstream->current_sample; } } /* if it's a framed encoding don't do more than one frame */ - if (samples_per_frame>1 && (vgmstream->samples_into_block%samples_per_frame)+samples_to_do>samples_per_frame) - samples_to_do = samples_per_frame - (vgmstream->samples_into_block%samples_per_frame); + if (samples_per_frame > 1 && (vgmstream->samples_into_block % samples_per_frame) + samples_to_do > samples_per_frame) + samples_to_do = samples_per_frame - (vgmstream->samples_into_block % samples_per_frame); return samples_to_do; } @@ -2133,7 +2139,7 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { * (only needed with the "play stream end after looping N times" option enabled) */ vgmstream->loop_count++; if (vgmstream->loop_target && vgmstream->loop_target == vgmstream->loop_count) { - vgmstream->loop_flag = 0; /* could be improved but works ok */ + vgmstream->loop_flag = 0; /* could be improved but works ok, will be restored on resets */ return 0; } @@ -2145,7 +2151,7 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { vgmstream->coding_type == coding_PSX || vgmstream->coding_type == coding_PSX_badflags) { int i; - for (i=0;ichannels;i++) { + for (i = 0; i < vgmstream->channels; i++) { vgmstream->loop_ch[i].adpcm_history1_16 = vgmstream->ch[i].adpcm_history1_16; vgmstream->loop_ch[i].adpcm_history2_16 = vgmstream->ch[i].adpcm_history2_16; vgmstream->loop_ch[i].adpcm_history1_32 = vgmstream->ch[i].adpcm_history1_32; @@ -2156,60 +2162,60 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { /* prepare certain codecs' internal state for looping */ - if (vgmstream->coding_type==coding_CRI_HCA) { - loop_hca(vgmstream->codec_data); + if (vgmstream->coding_type == coding_CRI_HCA) { + loop_hca(vgmstream->codec_data, vgmstream->loop_sample); } - if (vgmstream->coding_type==coding_EA_MT) { - seek_ea_mt(vgmstream, vgmstream->loop_start_sample); + if (vgmstream->coding_type == coding_EA_MT) { + seek_ea_mt(vgmstream, vgmstream->loop_sample); } #ifdef VGM_USE_VORBIS - if (vgmstream->coding_type==coding_OGG_VORBIS) { + if (vgmstream->coding_type == coding_OGG_VORBIS) { seek_ogg_vorbis(vgmstream, vgmstream->loop_sample); } - if (vgmstream->coding_type==coding_VORBIS_custom) { - seek_vorbis_custom(vgmstream, vgmstream->loop_start_sample); + if (vgmstream->coding_type == coding_VORBIS_custom) { + seek_vorbis_custom(vgmstream, vgmstream->loop_sample); } #endif #ifdef VGM_USE_FFMPEG - if (vgmstream->coding_type==coding_FFmpeg) { - seek_ffmpeg(vgmstream, vgmstream->loop_start_sample); + if (vgmstream->coding_type == coding_FFmpeg) { + seek_ffmpeg(vgmstream, vgmstream->loop_sample); } #endif #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) - if (vgmstream->coding_type==coding_MP4_AAC) { + if (vgmstream->coding_type == coding_MP4_AAC) { seek_mp4_aac(vgmstream, vgmstream->loop_sample); } #endif #ifdef VGM_USE_MAIATRAC3PLUS - if (vgmstream->coding_type==coding_AT3plus) { + if (vgmstream->coding_type == coding_AT3plus) { seek_at3plus(vgmstream, vgmstream->loop_sample); } #endif #ifdef VGM_USE_ATRAC9 - if (vgmstream->coding_type==coding_ATRAC9) { + if (vgmstream->coding_type == coding_ATRAC9) { seek_atrac9(vgmstream, vgmstream->loop_sample); } #endif #ifdef VGM_USE_CELT - if (vgmstream->coding_type==coding_CELT_FSB) { + if (vgmstream->coding_type == coding_CELT_FSB) { seek_celt_fsb(vgmstream, vgmstream->loop_sample); } #endif #ifdef VGM_USE_MPEG - if (vgmstream->coding_type==coding_MPEG_custom || - vgmstream->coding_type==coding_MPEG_ealayer3 || - vgmstream->coding_type==coding_MPEG_layer1 || - vgmstream->coding_type==coding_MPEG_layer2 || - vgmstream->coding_type==coding_MPEG_layer3) { + if (vgmstream->coding_type == coding_MPEG_custom || + vgmstream->coding_type == coding_MPEG_ealayer3 || + vgmstream->coding_type == coding_MPEG_layer1 || + vgmstream->coding_type == coding_MPEG_layer2 || + vgmstream->coding_type == coding_MPEG_layer3) { seek_mpeg(vgmstream, vgmstream->loop_sample); } #endif @@ -2221,7 +2227,7 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { } /* restore! */ - memcpy(vgmstream->ch,vgmstream->loop_ch,sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); + memcpy(vgmstream->ch, vgmstream->loop_ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); vgmstream->current_sample = vgmstream->loop_sample; vgmstream->samples_into_block = vgmstream->loop_samples_into_block; vgmstream->current_block_size = vgmstream->loop_block_size; @@ -2234,10 +2240,9 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) { /* is this the loop start? */ - if (!vgmstream->hit_loop && vgmstream->current_sample==vgmstream->loop_start_sample) { + if (!vgmstream->hit_loop && vgmstream->current_sample == vgmstream->loop_start_sample) { /* save! */ - memcpy(vgmstream->loop_ch,vgmstream->ch,sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); - + memcpy(vgmstream->loop_ch, vgmstream->ch, sizeof(VGMSTREAMCHANNEL)*vgmstream->channels); vgmstream->loop_sample = vgmstream->current_sample; vgmstream->loop_samples_into_block = vgmstream->samples_into_block; vgmstream->loop_block_size = vgmstream->current_block_size; @@ -2256,46 +2261,90 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { #define TEMPSIZE (256+32) char temp[TEMPSIZE]; const char* description; + double time_mm, time_ss, seconds; if (!vgmstream) { - snprintf(temp,TEMPSIZE, - "NULL VGMSTREAM"); + snprintf(temp,TEMPSIZE, "NULL VGMSTREAM"); concatn(length,desc,temp); return; } - snprintf(temp,TEMPSIZE, - "sample rate: %d Hz\n" - "channels: %d\n", - vgmstream->sample_rate, - vgmstream->channels); + snprintf(temp,TEMPSIZE, "sample rate: %d Hz\n", vgmstream->sample_rate); concatn(length,desc,temp); - if (vgmstream->loop_flag) { - snprintf(temp,TEMPSIZE, - "loop start: %d samples (%.4f seconds)\n" - "loop end: %d samples (%.4f seconds)\n", - vgmstream->loop_start_sample, - (double)vgmstream->loop_start_sample/vgmstream->sample_rate, - vgmstream->loop_end_sample, - (double)vgmstream->loop_end_sample/vgmstream->sample_rate); + snprintf(temp,TEMPSIZE, "channels: %d\n", vgmstream->channels); + concatn(length,desc,temp); + + { + int output_channels = 0; + mixing_info(vgmstream, NULL, &output_channels); + + if (output_channels != vgmstream->channels) { + snprintf(temp,TEMPSIZE, "input channels: %d\n", vgmstream->channels); /* repeated but mainly for plugins */ + concatn(length,desc,temp); + snprintf(temp,TEMPSIZE, "output channels: %d\n", output_channels); + concatn(length,desc,temp); + } + } + + if (vgmstream->channel_layout) { + int cl = vgmstream->channel_layout; + + /* not "channel layout: " to avoid mixups with "layout: " */ + snprintf(temp,TEMPSIZE, "channel mask: 0x%x /", vgmstream->channel_layout); + concatn(length,desc,temp); + if (cl & speaker_FL) concatn(length,desc," FL"); + if (cl & speaker_FR) concatn(length,desc," FR"); + if (cl & speaker_FC) concatn(length,desc," FC"); + if (cl & speaker_LFE) concatn(length,desc," LFE"); + if (cl & speaker_BL) concatn(length,desc," BL"); + if (cl & speaker_BR) concatn(length,desc," BR"); + if (cl & speaker_FLC) concatn(length,desc," FLC"); + if (cl & speaker_FRC) concatn(length,desc," FRC"); + if (cl & speaker_BC) concatn(length,desc," BC"); + if (cl & speaker_SL) concatn(length,desc," SL"); + if (cl & speaker_SR) concatn(length,desc," SR"); + if (cl & speaker_TC) concatn(length,desc," TC"); + if (cl & speaker_TFL) concatn(length,desc," TFL"); + if (cl & speaker_TFC) concatn(length,desc," TFC"); + if (cl & speaker_TFR) concatn(length,desc," TFR"); + if (cl & speaker_TBL) concatn(length,desc," TBL"); + if (cl & speaker_TBC) concatn(length,desc," TBC"); + if (cl & speaker_TBR) concatn(length,desc," TBR"); + concatn(length,desc,"\n"); + } + + if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) { + if (!vgmstream->loop_flag) { + concatn(length,desc,"looping: disabled\n"); + } + + seconds = (double)vgmstream->loop_start_sample / vgmstream->sample_rate; + time_mm = (int)(seconds / 60.0); + time_ss = seconds - time_mm * 60.0f; + snprintf(temp,TEMPSIZE, "loop start: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_start_sample, time_mm, time_ss); + concatn(length,desc,temp); + + seconds = (double)vgmstream->loop_end_sample / vgmstream->sample_rate; + time_mm = (int)(seconds / 60.0); + time_ss = seconds - time_mm * 60.0f; + snprintf(temp,TEMPSIZE, "loop end: %d samples (%1.0f:%06.3f seconds)\n", vgmstream->loop_end_sample, time_mm, time_ss); concatn(length,desc,temp); } - snprintf(temp,TEMPSIZE, - "stream total samples: %d (%.4f seconds)\n", - vgmstream->num_samples, - (double)vgmstream->num_samples/vgmstream->sample_rate); + seconds = (double)vgmstream->num_samples / vgmstream->sample_rate; + time_mm = (int)(seconds / 60.0); + time_ss = seconds - time_mm * 60.0; + snprintf(temp,TEMPSIZE, "stream total samples: %d (%1.0f:%06.3f seconds)\n", vgmstream->num_samples, time_mm, time_ss); concatn(length,desc,temp); - snprintf(temp,TEMPSIZE, - "encoding: "); + snprintf(temp,TEMPSIZE, "encoding: "); concatn(length,desc,temp); switch (vgmstream->coding_type) { - - //todo codec bugs with layout inside layouts (ex. TXTP) #ifdef VGM_USE_FFMPEG + case coding_FFmpeg: { + //todo codec bugs with layout inside layouts (ex. TXTP) ffmpeg_codec_data *data = NULL; if (vgmstream->layout_type == layout_layered) { @@ -2314,61 +2363,68 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { if (data) { if (data->codec && data->codec->long_name) { - snprintf(temp,TEMPSIZE,"%s",data->codec->long_name); + snprintf(temp,TEMPSIZE, "%s",data->codec->long_name); } else if (data->codec && data->codec->name) { - snprintf(temp,TEMPSIZE,"%s",data->codec->name); + snprintf(temp,TEMPSIZE, "%s",data->codec->name); } else { - snprintf(temp,TEMPSIZE,"FFmpeg (unknown codec)"); + snprintf(temp,TEMPSIZE, "FFmpeg (unknown codec)"); } } else { - snprintf(temp,TEMPSIZE,"FFmpeg"); + snprintf(temp,TEMPSIZE, "FFmpeg"); } break; } #endif default: description = get_vgmstream_coding_description(vgmstream->coding_type); - if (!description) - description = "CANNOT DECODE"; - snprintf(temp,TEMPSIZE,"%s",description); + if (!description) description = "CANNOT DECODE"; + snprintf(temp,TEMPSIZE, "%s",description); break; } concatn(length,desc,temp); + concatn(length,desc,"\n"); - snprintf(temp,TEMPSIZE, - "\nlayout: "); + snprintf(temp,TEMPSIZE, "layout: "); concatn(length,desc,temp); + { + VGMSTREAM* vgmstreamsub = NULL; - description = get_vgmstream_layout_description(vgmstream->layout_type); - if (!description) - description = "INCONCEIVABLE"; - switch (vgmstream->layout_type) { - case layout_layered: - snprintf(temp,TEMPSIZE,"%s (%i layers)",description, ((layered_layout_data*)vgmstream->layout_data)->layer_count); - break; - case layout_segmented: - snprintf(temp,TEMPSIZE,"%s (%i segments)",description, ((segmented_layout_data*)vgmstream->layout_data)->segment_count); - break; - default: - snprintf(temp,TEMPSIZE,"%s",description); - break; + description = get_vgmstream_layout_description(vgmstream->layout_type); + if (!description) description = "INCONCEIVABLE"; + + if (vgmstream->layout_type == layout_layered) { + vgmstreamsub = ((layered_layout_data*)vgmstream->layout_data)->layers[0]; + snprintf(temp,TEMPSIZE, "%s (%i layers)", description, ((layered_layout_data*)vgmstream->layout_data)->layer_count); + } + else if (vgmstream->layout_type == layout_segmented) { + snprintf(temp,TEMPSIZE, "%s (%i segments)", description, ((segmented_layout_data*)vgmstream->layout_data)->segment_count); + vgmstreamsub = ((segmented_layout_data*)vgmstream->layout_data)->segments[0]; + } + else { + snprintf(temp,TEMPSIZE, "%s",description); + } + concatn(length,desc,temp); + + /* layouts can contain layouts infinitely let's leave it at one level deep (most common) */ + if (vgmstreamsub && vgmstreamsub->layout_type == layout_layered) { + description = get_vgmstream_layout_description(vgmstreamsub->layout_type); + snprintf(temp,TEMPSIZE, " + %s (%i layers)",description, ((layered_layout_data*)vgmstreamsub->layout_data)->layer_count); + concatn(length,desc,temp); + } + else if (vgmstreamsub && vgmstreamsub->layout_type == layout_segmented) { + description = get_vgmstream_layout_description(vgmstreamsub->layout_type); + snprintf(temp,TEMPSIZE, " + %s (%i segments)",description, ((segmented_layout_data*)vgmstream->layout_data)->segment_count); + concatn(length,desc,temp); + } } - concatn(length,desc,temp); - - snprintf(temp,TEMPSIZE, - "\n"); - concatn(length,desc,temp); + concatn(length,desc,"\n"); if (vgmstream->layout_type == layout_interleave && vgmstream->channels > 1) { - snprintf(temp,TEMPSIZE, - "interleave: %#x bytes\n", - (int32_t)vgmstream->interleave_block_size); + snprintf(temp,TEMPSIZE, "interleave: %#x bytes\n", (int32_t)vgmstream->interleave_block_size); concatn(length,desc,temp); if (vgmstream->interleave_last_block_size) { - snprintf(temp,TEMPSIZE, - "interleave last block: %#x bytes\n", - (int32_t)vgmstream->interleave_last_block_size); + snprintf(temp,TEMPSIZE, "interleave last block: %#x bytes\n", (int32_t)vgmstream->interleave_last_block_size); concatn(length,desc,temp); } } @@ -2384,9 +2440,7 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { case coding_WWISE_IMA: case coding_REF_IMA: case coding_PSX_cfg: - snprintf(temp,TEMPSIZE, - "frame size: %#x bytes\n", - (int32_t)vgmstream->interleave_block_size); + snprintf(temp,TEMPSIZE, "frame size: %#x bytes\n", (int32_t)vgmstream->interleave_block_size); concatn(length,desc,temp); break; default: @@ -2394,43 +2448,34 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) { } } - snprintf(temp,TEMPSIZE, - "metadata from: "); + snprintf(temp,TEMPSIZE, "metadata from: "); concatn(length,desc,temp); switch (vgmstream->meta_type) { default: description = get_vgmstream_meta_description(vgmstream->meta_type); - if (!description) - description = "THEY SHOULD HAVE SENT A POET"; - snprintf(temp,TEMPSIZE,"%s",description); + if (!description) description = "THEY SHOULD HAVE SENT A POET"; + snprintf(temp,TEMPSIZE, "%s", description); break; } concatn(length,desc,temp); + concatn(length,desc,"\n"); - snprintf(temp,TEMPSIZE, - "\nbitrate: %d kbps", - get_vgmstream_average_bitrate(vgmstream) / 1000); + snprintf(temp,TEMPSIZE, "bitrate: %d kbps\n", get_vgmstream_average_bitrate(vgmstream) / 1000); //todo \n? concatn(length,desc,temp); /* only interesting if more than one */ if (vgmstream->num_streams > 1) { - snprintf(temp,TEMPSIZE, - "\nstream count: %d", - vgmstream->num_streams); + snprintf(temp,TEMPSIZE, "stream count: %d\n", vgmstream->num_streams); concatn(length,desc,temp); } if (vgmstream->num_streams > 1) { - snprintf(temp,TEMPSIZE, - "\nstream index: %d", - vgmstream->stream_index == 0 ? 1 : vgmstream->stream_index); + snprintf(temp,TEMPSIZE, "stream index: %d\n", vgmstream->stream_index == 0 ? 1 : vgmstream->stream_index); concatn(length,desc,temp); } if (vgmstream->stream_name[0] != '\0') { - snprintf(temp,TEMPSIZE, - "\nstream name: %s", - vgmstream->stream_name); + snprintf(temp,TEMPSIZE, "stream name: %s\n", vgmstream->stream_name); concatn(length,desc,temp); } } @@ -2459,21 +2504,25 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea if (opened_vgmstream->channels != 1) return; + /* custom codec/layouts aren't designed for this (should never get here anyway) */ + if (opened_vgmstream->codec_data || opened_vgmstream->layout_data) + return; + /* vgmstream's layout stuff currently assumes a single file */ // fastelbja : no need ... this one works ok with dual file //if (opened_vgmstream->layout != layout_none) return; //todo force layout_none if layout_interleave? - streamFile->get_name(streamFile,new_filename,sizeof(new_filename)); - if (strlen(new_filename)<2) return; /* we need at least a base and a name ending to replace */ - + get_streamfile_name(streamFile,new_filename,sizeof(new_filename)); + if (strlen(new_filename) < 2) return; /* we need at least a base and a name ending to replace */ + ext = (char *)filename_extension(new_filename); if (ext-new_filename >= 1 && ext[-1]=='.') ext--; /* including "." */ /* find pair from base name and modify new_filename with the opposite */ dfs_pair_count = (sizeof(dfs_pairs)/sizeof(dfs_pairs[0])); - for (i = 0; dfs_pair == -1 && i< dfs_pair_count; i++) { - for (j=0; dfs_pair==-1 && j<2; j++) { + for (i = 0; dfs_pair == -1 && i < dfs_pair_count; i++) { + for (j = 0; dfs_pair == -1 && j < 2; j++) { const char * this_suffix = dfs_pairs[i][j]; size_t this_suffix_len = strlen(dfs_pairs[i][j]); const char * other_suffix = dfs_pairs[i][j^1]; @@ -2503,7 +2552,7 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea /* try to init other channel (new_filename now has the opposite name) */ - dual_streamFile = streamFile->open(streamFile,new_filename,STREAMFILE_DEFAULT_BUFFER_SIZE); + dual_streamFile = open_streamfile(streamFile,new_filename); if (!dual_streamFile) goto fail; new_vgmstream = init_vgmstream_function(dual_streamFile); /* use the init that just worked, no other should work */ @@ -2528,7 +2577,7 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea } /* check these even if there is no loop, because they should then be zero in both - * Homura PS2 right channel doesn't have loop points so it's ignored */ + * (Homura PS2 right channel doesn't have loop points so this check is ignored) */ if (new_vgmstream->meta_type != meta_PS2_SMPL && !(new_vgmstream->loop_flag == opened_vgmstream->loop_flag && new_vgmstream->loop_start_sample== opened_vgmstream->loop_start_sample && @@ -2588,43 +2637,47 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea /* discard the second VGMSTREAM */ free(new_vgmstream); + + mixing_update_channel(opened_vgmstream); /* notify of new channel hacked-in */ } + return; fail: + close_vgmstream(new_vgmstream); return; } /* average bitrate helper to get STREAMFILE for a channel, since some codecs may use their own */ static STREAMFILE * get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM * vgmstream, int channel) { - if (vgmstream->coding_type==coding_NWA) { + if (vgmstream->coding_type == coding_NWA) { nwa_codec_data *data = vgmstream->codec_data; return (data && data->nwa) ? data->nwa->file : NULL; } - if (vgmstream->coding_type==coding_ACM) { + if (vgmstream->coding_type == coding_ACM) { acm_codec_data *data = vgmstream->codec_data; return (data && data->handle) ? data->streamfile : NULL; } #ifdef VGM_USE_VORBIS - if (vgmstream->coding_type==coding_OGG_VORBIS) { + if (vgmstream->coding_type == coding_OGG_VORBIS) { ogg_vorbis_codec_data *data = vgmstream->codec_data; return data ? data->ov_streamfile.streamfile : NULL; } #endif - if (vgmstream->coding_type==coding_CRI_HCA) { + if (vgmstream->coding_type == coding_CRI_HCA) { hca_codec_data *data = vgmstream->codec_data; return data ? data->streamfile : NULL; } #ifdef VGM_USE_FFMPEG - if (vgmstream->coding_type==coding_FFmpeg) { + if (vgmstream->coding_type == coding_FFmpeg) { ffmpeg_codec_data *data = vgmstream->codec_data; return data ? data->streamfile : NULL; } #endif #if defined(VGM_USE_MP4V2) && defined(VGM_USE_FDKAAC) - if (vgmstream->coding_type==coding_MP4_AAC) { + if (vgmstream->coding_type == coding_MP4_AAC) { mp4_aac_codec_data *data = vgmstream->codec_data; return data ? data->if_file.streamfile : NULL; } @@ -2633,100 +2686,96 @@ static STREAMFILE * get_vgmstream_average_bitrate_channel_streamfile(VGMSTREAM * return vgmstream->ch[channel].streamfile; } -static int get_vgmstream_average_bitrate_from_size(size_t size, int sample_rate, int length_samples) { +static int get_vgmstream_file_bitrate_from_size(size_t size, int sample_rate, int length_samples) { + if (sample_rate == 0 || length_samples == 0) return 0; + if (length_samples < 100) return 0; /* ignore stupid bitrates caused by some segments */ return (int)((int64_t)size * 8 * sample_rate / length_samples); } -static int get_vgmstream_average_bitrate_from_streamfile(STREAMFILE * streamfile, int sample_rate, int length_samples) { - return get_vgmstream_average_bitrate_from_size(get_streamfile_size(streamfile), sample_rate, length_samples); +static int get_vgmstream_file_bitrate_from_streamfile(STREAMFILE * streamfile, int sample_rate, int length_samples) { + if (streamfile == NULL) return 0; + return get_vgmstream_file_bitrate_from_size(get_streamfile_size(streamfile), sample_rate, length_samples); } -/* Return the average bitrate in bps of all unique files contained within this stream. */ -int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream) { - STREAMFILE *streamfiles[64]; - const size_t streamfiles_max = 64; /* arbitrary max, */ - size_t streamfiles_size = 0; - size_t streams_size = 0; - unsigned int ch, sub; - +static int get_vgmstream_file_bitrate_main(VGMSTREAM * vgmstream, STREAMFILE **streamfile_pointers, int *pointers_count, int pointers_max) { + int sub, ch; int bitrate = 0; - int sample_rate = vgmstream->sample_rate; - int length_samples = vgmstream->num_samples; - if (!sample_rate || !length_samples) - return 0; + /* Recursively get bitrate and fill the list of streamfiles if needed (to filter), + * since layouts can include further vgmstreams that may also share streamfiles. + * + * Because of how data, layers and segments can be combined it's possible to + * fool this in various ways; metas should report stream_size in complex cases + * to get accurate bitrates (particularly for subsongs). */ - /* subsongs need to report this to properly calculate */ if (vgmstream->stream_size) { - return get_vgmstream_average_bitrate_from_size(vgmstream->stream_size, sample_rate, length_samples); + bitrate = get_vgmstream_file_bitrate_from_size(vgmstream->stream_size, vgmstream->sample_rate, vgmstream->num_samples); } - - //todo bitrate bugs with layout inside layouts (ex. TXTP) - /* make a list of used streamfiles (repeats will be filtered below) */ - if (vgmstream->layout_type==layout_segmented) { + else if (vgmstream->layout_type == layout_segmented) { segmented_layout_data *data = (segmented_layout_data *) vgmstream->layout_data; for (sub = 0; sub < data->segment_count; sub++) { - streams_size += data->segments[sub]->stream_size; - for (ch = 0; ch < data->segments[sub]->channels; ch++) { - if (streamfiles_size >= streamfiles_max) continue; - streamfiles[streamfiles_size] = get_vgmstream_average_bitrate_channel_streamfile(data->segments[sub], ch); - streamfiles_size++; - } + bitrate += get_vgmstream_file_bitrate_main(data->segments[sub], streamfile_pointers, pointers_count, pointers_max); } + bitrate = bitrate / data->segment_count; } - else if (vgmstream->layout_type==layout_layered) { + else if (vgmstream->layout_type == layout_layered) { layered_layout_data *data = vgmstream->layout_data; for (sub = 0; sub < data->layer_count; sub++) { - streams_size += data->layers[sub]->stream_size; - for (ch = 0; ch < data->layers[sub]->channels; ch++) { - if (streamfiles_size >= streamfiles_max) continue; - streamfiles[streamfiles_size] = get_vgmstream_average_bitrate_channel_streamfile(data->layers[sub], ch); - streamfiles_size++; - } + bitrate += get_vgmstream_file_bitrate_main(data->layers[sub], streamfile_pointers, pointers_count, pointers_max); } + bitrate = bitrate / data->layer_count; } else { - for (ch = 0; ch < vgmstream->channels; ch++) { - if (streamfiles_size >= streamfiles_max) - continue; - streamfiles[streamfiles_size] = get_vgmstream_average_bitrate_channel_streamfile(vgmstream, ch); - streamfiles_size++; - } - } - - /* could have a sum of all sub-VGMSTREAMs */ - if (streams_size) { - return get_vgmstream_average_bitrate_from_size(streams_size, sample_rate, length_samples); - } - - /* compare files by absolute paths, so bitrate doesn't multiply when the same STREAMFILE is - * reopened per channel, also skipping repeated pointers. */ - { + /* Add channel bitrate if streamfile hasn't been used before (comparing files + * by absolute paths), so bitrate doesn't multiply when the same STREAMFILE is + * reopened per channel, also skipping repeated pointers. */ char path_current[PATH_LIMIT]; char path_compare[PATH_LIMIT]; - unsigned int i, j; + int is_unique = 1; - for (i = 0; i < streamfiles_size; i++) { - STREAMFILE * currentFile = streamfiles[i]; + for (ch = 0; ch < vgmstream->channels; ch++) { + STREAMFILE * currentFile = get_vgmstream_average_bitrate_channel_streamfile(vgmstream, ch); if (!currentFile) continue; get_streamfile_name(currentFile, path_current, sizeof(path_current)); - for (j = 0; j < i; j++) { - STREAMFILE * compareFile = streamfiles[j]; + for (sub = 0; sub < *pointers_count; sub++) { + STREAMFILE * compareFile = streamfile_pointers[sub]; if (!compareFile) continue; - if (currentFile == compareFile) + if (currentFile == compareFile) { + is_unique = 0; break; + } get_streamfile_name(compareFile, path_compare, sizeof(path_compare)); - if (strcmp(path_current, path_compare) == 0) + if (strcmp(path_current, path_compare) == 0) { + is_unique = 0; break; + } } - if (i == j) { /* current STREAMFILE hasn't appeared previously */ - bitrate += get_vgmstream_average_bitrate_from_streamfile(currentFile, sample_rate, length_samples); + if (is_unique) { + if (*pointers_count >= pointers_max) goto fail; + streamfile_pointers[*pointers_count] = currentFile; + (*pointers_count)++; + + bitrate += get_vgmstream_file_bitrate_from_streamfile(currentFile, vgmstream->sample_rate, vgmstream->num_samples); } } } return bitrate; +fail: + return 0; +} + +/* Return the average bitrate in bps of all unique data contained within this stream. + * This is the bitrate of the *file*, as opposed to the bitrate of the *codec*, meaning + * it counts extra data like block headers and padding. While this can be surprising + * sometimes (as it's often higher than common codec bitrates) it isn't wrong per se. */ +int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream) { + const size_t pointers_max = 128; /* arbitrary max, but +100 segments have been observed */ + STREAMFILE *streamfile_pointers[128]; /* list already used streamfiles */ + int pointers_count = 0; + + return get_vgmstream_file_bitrate_main(vgmstream, streamfile_pointers, &pointers_count, pointers_max); } @@ -2745,9 +2794,14 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s int is_stereo_codec = 0; + if (vgmstream == NULL) { + VGM_LOG("VGMSTREAM: buggy code (null vgmstream)\n"); + goto fail; + } + + /* stream/offsets not needed, managed by layout */ - if (vgmstream->layout_type == layout_aix || - vgmstream->layout_type == layout_segmented || + if (vgmstream->layout_type == layout_segmented || vgmstream->layout_type == layout_layered) return 1; @@ -2780,15 +2834,22 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s } /* stereo codecs interleave in 2ch pairs (interleave size should still be: full_block_size / channels) */ - if (vgmstream->layout_type == layout_interleave && vgmstream->coding_type == coding_XBOX_IMA) { + if (vgmstream->layout_type == layout_interleave && + (vgmstream->coding_type == coding_XBOX_IMA || vgmstream->coding_type == coding_MTAF)) { is_stereo_codec = 1; } - streamFile->get_name(streamFile,filename,sizeof(filename)); + if (streamFile == NULL || start_offset < 0) { + VGM_LOG("VGMSTREAM: buggy code (null streamfile / wrong start_offset)\n"); + goto fail; + } + + + get_streamfile_name(streamFile,filename,sizeof(filename)); /* open the file for reading by each channel */ { if (!use_streamfile_per_channel) { - file = streamFile->open(streamFile,filename, STREAMFILE_DEFAULT_BUFFER_SIZE); + file = open_streamfile(streamFile,filename); if (!file) goto fail; } @@ -2805,7 +2866,7 @@ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t s /* open new one if needed */ if (use_streamfile_per_channel) { - file = streamFile->open(streamFile,filename, STREAMFILE_DEFAULT_BUFFER_SIZE); + file = open_streamfile(streamFile,filename); if (!file) goto fail; } diff --git a/Frameworks/vgmstream/vgmstream/src/vgmstream.h b/Frameworks/vgmstream/vgmstream/src/vgmstream.h index 0ba73b074..f07c4415a 100644 --- a/Frameworks/vgmstream/vgmstream/src/vgmstream.h +++ b/Frameworks/vgmstream/vgmstream/src/vgmstream.h @@ -5,38 +5,48 @@ #ifndef _VGMSTREAM_H #define _VGMSTREAM_H +/* reasonable limits */ enum { PATH_LIMIT = 32768 }; -enum { STREAM_NAME_SIZE = 255 }; /* reasonable max */ +enum { STREAM_NAME_SIZE = 255 }; +enum { VGMSTREAM_MAX_CHANNELS = 64 }; +enum { VGMSTREAM_MIN_SAMPLE_RATE = 300 }; /* 300 is Wwise min */ +enum { VGMSTREAM_MAX_SAMPLE_RATE = 192000 }; /* found in some FSB5 */ +enum { VGMSTREAM_MAX_SUBSONGS = 65535 }; #include "streamfile.h" /* Due mostly to licensing issues, Vorbis, MPEG, G.722.1, etc decoding is done by external libraries. * Libs are disabled by default, defined on compile-time for builds that support it */ -#define VGM_USE_VORBIS -#define VGM_USE_MPEG -#define VGM_USE_G7221 -#define VGM_USE_G719 +//#define VGM_USE_VORBIS +//#define VGM_USE_MPEG +//#define VGM_USE_G7221 +//#define VGM_USE_G719 //#define VGM_USE_MP4V2 //#define VGM_USE_FDKAAC //#define VGM_USE_MAIATRAC3PLUS -#define VGM_USE_FFMPEG -#define VGM_USE_ATRAC9 +//#define VGM_USE_FFMPEG +//#define VGM_USE_ATRAC9 //#define VGM_USE_CELT #ifdef VGM_USE_VORBIS -#ifdef __APPLE__ -#define __MACOSX__ -#endif #include #endif #ifdef VGM_USE_MPEG +#ifdef __MACOSX__ #include +#else +#include +#endif #endif #ifdef VGM_USE_G7221 +#ifdef __MACOSX__ #include +#else +#include +#endif #endif #ifdef VGM_USE_MP4V2 @@ -126,6 +136,7 @@ typedef enum { coding_WV6_IMA, /* Gorilla Systems WV6 4-bit IMA ADPCM */ coding_ALP_IMA, /* High Voltage ALP 4-bit IMA ADPCM */ coding_FFTA2_IMA, /* Final Fantasy Tactics A2 4-bit IMA ADPCM */ + coding_BLITZ_IMA, /* Blitz Games 4-bit IMA ADPCM */ coding_MS_IMA, /* Microsoft IMA ADPCM */ coding_XBOX_IMA, /* XBOX IMA ADPCM */ @@ -147,10 +158,12 @@ typedef enum { coding_MSADPCM_int, /* Microsoft ADPCM (mono) */ coding_MSADPCM_ck, /* Microsoft ADPCM (Cricket Audio variation) */ coding_WS, /* Westwood Studios VBR ADPCM */ - coding_AICA, /* Yamaha AICA ADPCM (stereo) */ - coding_AICA_int, /* Yamaha AICA ADPCM (mono/interleave) */ - coding_YAMAHA, /* Yamaha ADPCM */ - coding_YAMAHA_NXAP, /* Yamaha ADPCM (NXAP variation) */ + + coding_YAMAHA, /* Yamaha ADPCM (stereo) */ + coding_YAMAHA_int, /* Yamaha ADPCM (mono/interleave) */ + coding_ASKA, /* Aska ADPCM */ + coding_NXAP, /* NXAP ADPCM */ + coding_NDS_PROCYON, /* Procyon Studio ADPCM */ coding_L5_555, /* Level-5 0x555 ADPCM */ coding_LSF, /* lsf ADPCM (Fastlane Street Racing iPhone)*/ @@ -270,7 +283,6 @@ typedef enum { layout_blocked_vs_square, /* otherwise odd */ - layout_aix, /* CRI AIX's wheels within wheels */ layout_segmented, /* song divided in segments (song sections) */ layout_layered, /* song divided in layers (song channels) */ @@ -337,7 +349,7 @@ typedef enum { meta_XA, /* CD-ROM XA */ meta_PS2_SShd, /* .ADS with SShd header */ - meta_PS2_NPSF, /* Namco Production Sound File */ + meta_NPS, meta_PS2_RXWS, /* Sony games (Genji, Okage Shadow King, Arc The Lad Twilight of Spirits) */ meta_PS2_RAW, /* RAW Interleaved Format */ meta_PS2_EXST, /* Shadow of Colossus EXST */ @@ -378,7 +390,7 @@ typedef enum { meta_MUSX_V201, /* Sphinx and the cursed Mummy */ meta_LEG, /* Legaia 2 [no header_id] */ meta_FILP, /* Resident Evil - Dead Aim */ - meta_IKM, /* Zwei! */ + meta_IKM, meta_SFS, /* Baroque */ meta_BG00, /* Ibara, Mushihimesama */ meta_PS2_RSTM, /* Midnight Club 3 */ @@ -451,7 +463,7 @@ typedef enum { meta_STR_ASR, /* Donkey Kong Jet Race */ meta_ZWDSP, /* Zack and Wiki */ meta_VGS, /* Guitar Hero Encore - Rocks the 80s */ - meta_DC_DCSW_DCS, /* Evil Twin - Cypriens Chronicles (DC) */ + meta_DCS_WAV, meta_SMP, meta_WII_SNG, /* Excite Trucks */ meta_MUL, @@ -568,7 +580,7 @@ typedef enum { meta_BAF, /* Bizarre Creations (Blur, James Bond) */ meta_XVAG, /* Ratchet & Clank Future: Quest for Booty (PS3) */ meta_PS3_CPS, /* Eternal Sonata (PS3) */ - meta_PS3_MSF, /* MSF header */ + meta_MSF, meta_NUB_VAG, /* Namco VAG from NUB archives */ meta_PS3_PAST, /* Bakugan Battle Brawlers (PS3) */ meta_SGXD, /* Sony: Folklore, Genji, Tokyo Jungle (PS3), Brave Story, Kurohyo (PSP) */ @@ -589,7 +601,7 @@ typedef enum { meta_OTNS_ADP, /* Omikron: The Nomad Soul .adp (PC/DC) */ meta_EB_SFX, /* Excitebots .sfx */ meta_EB_SF0, /* Excitebots .sf0 */ - meta_PS2_MTAF, /* Metal Gear Solid 3 MTAF */ + meta_MTAF, meta_PS2_VAG1, /* Metal Gear Solid 3 VAG1 */ meta_PS2_VAG2, /* Metal Gear Solid 3 VAG2 */ meta_TUN, /* LEGO Racers (PC) */ @@ -627,7 +639,7 @@ typedef enum { meta_TA_AAC_X360, /* tri-Ace AAC (Star Ocean 4, End of Eternity, Infinite Undiscovery) */ meta_TA_AAC_PS3, /* tri-Ace AAC (Star Ocean International, Resonance of Fate) */ meta_TA_AAC_MOBILE, /* tri-Ace AAC (Star Ocean Anamnesis, Heaven x Inferno) */ - meta_PS3_MTA2, /* Metal Gear Solid 4 MTA2 */ + meta_MTA2, meta_NGC_ULW, /* Burnout 1 (GC only) */ meta_PC_XA30, /* Driver - Parallel Lines (PC) */ meta_WII_04SW, /* Driver - Parallel Lines (Wii) */ @@ -724,15 +736,63 @@ typedef enum { meta_GIN, meta_DSF, meta_208, + meta_DSP_DS2, + meta_MUS_VC, + meta_STRM_ABYLIGHT, + meta_MSF_KONAMI, + meta_XWMA_KONAMI, + meta_9TAV, } meta_t; +/* standard WAVEFORMATEXTENSIBLE speaker positions */ +typedef enum { + speaker_FL = (1 << 0), /* front left */ + speaker_FR = (1 << 1), /* front right */ + speaker_FC = (1 << 2), /* front center */ + speaker_LFE = (1 << 3), /* low frequency effects */ + speaker_BL = (1 << 4), /* back left */ + speaker_BR = (1 << 5), /* back right */ + speaker_FLC = (1 << 6), /* front left center */ + speaker_FRC = (1 << 7), /* front right center */ + speaker_BC = (1 << 8), /* back center */ + speaker_SL = (1 << 9), /* side left */ + speaker_SR = (1 << 10), /* side right */ + + speaker_TC = (1 << 11), /* top center*/ + speaker_TFL = (1 << 12), /* top front left */ + speaker_TFC = (1 << 13), /* top front center */ + speaker_TFR = (1 << 14), /* top front right */ + speaker_TBL = (1 << 15), /* top back left */ + speaker_TBC = (1 << 16), /* top back center */ + speaker_TBR = (1 << 17), /* top back left */ + +} speaker_t; + +/* typical mappings that metas may use to set channel_layout (but plugin must actually use it) + * (in order, so 3ch file could be mapped to FL FR FC or FL FR LFE but not LFE FL FR) */ +typedef enum { + mapping_MONO = speaker_FC, + mapping_STEREO = speaker_FL | speaker_FR, + mapping_2POINT1 = speaker_FL | speaker_FR | speaker_LFE, + mapping_2POINT1_xiph = speaker_FL | speaker_FR | speaker_FC, + mapping_QUAD = speaker_FL | speaker_FR | speaker_BL | speaker_BR, + mapping_QUAD_surround = speaker_FL | speaker_FR | speaker_FC | speaker_BC, + mapping_5POINT0 = speaker_FL | speaker_FR | speaker_LFE | speaker_BL | speaker_BR, + mapping_5POINT0_xiph = speaker_FL | speaker_FR | speaker_FC | speaker_BL | speaker_BR, + mapping_5POINT1 = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BL | speaker_BR, + mapping_5POINT1_surround = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_SL | speaker_SR, + mapping_7POINT0 = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BC | speaker_FLC | speaker_FRC, + mapping_7POINT1 = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BL | speaker_BR | speaker_FLC | speaker_FRC, + mapping_7POINT1_surround = speaker_FL | speaker_FR | speaker_FC | speaker_LFE | speaker_BL | speaker_BR | speaker_SL | speaker_SR, +} mapping_t; + /* info for a single vgmstream channel */ typedef struct { - STREAMFILE * streamfile; /* file used by this channel */ + STREAMFILE * streamfile; /* file used by this channel */ off_t channel_start_offset; /* where data for this channel begins */ - off_t offset; /* current location in the file */ + off_t offset; /* current location in the file */ off_t frame_header_offset; /* offset of the current frame header (for WS) */ int samples_left_in_frame; /* for WS */ @@ -740,7 +800,7 @@ typedef struct { /* format specific */ /* adpcm */ - int16_t adpcm_coef[16]; /* for formats with decode coefficients built in */ + int16_t adpcm_coef[16]; /* for formats with decode coefficients built in */ int32_t adpcm_coef_3by32[0x60]; /* for Level-5 0x555 */ union { int16_t adpcm_history1_16; /* previous sample */ @@ -778,7 +838,7 @@ typedef struct { /* main vgmstream info */ typedef struct { - /* basics */ + /* basic config */ int32_t num_samples; /* the actual max number of samples */ int32_t sample_rate; /* sample rate in Hz */ int channels; /* number of channels */ @@ -786,26 +846,27 @@ typedef struct { layout_t layout_type; /* type of layout */ meta_t meta_type; /* type of metadata */ - /* looping */ + /* loopin config */ int loop_flag; /* is this stream looped? */ int32_t loop_start_sample; /* first sample of the loop (included in the loop) */ int32_t loop_end_sample; /* last sample of the loop (not included in the loop) */ - /* layouts/block */ + /* layouts/block config */ size_t interleave_block_size; /* interleave, or block/frame size (depending on the codec) */ size_t interleave_last_block_size; /* smaller interleave for last block */ - /* subsongs */ + /* subsong config */ int num_streams; /* for multi-stream formats (0=not set/one stream, 1=one stream) */ int stream_index; /* selected subsong (also 1-based) */ size_t stream_size; /* info to properly calculate bitrate in case of subsongs */ char stream_name[STREAM_NAME_SIZE]; /* name of the current stream (info), if the file stores it and it's filled */ - /* config */ + /* mapping config (info for plugins) */ + uint32_t channel_layout; /* order: FL FR FC LFE BL BR FLC FRC BC SL SR etc (WAVEFORMATEX flags where FL=lowest bit set) */ + + /* other config */ int allow_dual_stereo; /* search for dual stereo (file_L.ext + file_R.ext = single stereo file) */ - uint32_t channel_mask; /* to silence crossfading subsongs/layers */ - int channel_mappings_on; /* channel mappings are active */ - int channel_mappings[32]; /* swap channel "i" with "[i]" */ + /* config requests, players must read and honor these values */ /* (ideally internally would work as a player, but for now player must do it manually) */ double config_loop_count; @@ -816,11 +877,6 @@ typedef struct { int config_ignore_fade; - /* channel state */ - VGMSTREAMCHANNEL * ch; /* pointer to array of channels */ - VGMSTREAMCHANNEL * start_ch; /* copies of channel status as they were at the beginning of the stream */ - VGMSTREAMCHANNEL * loop_ch; /* copies of channel status as they were at the loop point */ - /* layout/block state */ size_t full_block_size; /* actual data size of an entire block (ie. may be fixed, include padding/headers, etc) */ int32_t current_sample; /* number of samples we've passed (for loop detection) */ @@ -830,7 +886,7 @@ typedef struct { size_t current_block_samples; /* size in samples of the block we're in now (used over current_block_size if possible) */ off_t next_block_offset; /* offset of header of the next block */ /* layout/block loop state */ - int32_t loop_sample; /* saved from current_sample, should be loop_start_sample... */ + int32_t loop_sample; /* saved from current_sample (same as loop_start_sample, but more state-like) */ int32_t loop_samples_into_block;/* saved from samples_into_block */ off_t loop_block_offset; /* saved from current_block_offset */ size_t loop_block_size; /* saved from current_block_size */ @@ -842,22 +898,28 @@ typedef struct { int loop_count; /* counter of complete loops (1=looped once) */ int loop_target; /* max loops before continuing with the stream end (loops forever if not set) */ - /* decoder specific */ + /* decoder config/state */ int codec_endian; /* little/big endian marker; name is left vague but usually means big endian */ - int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to the codec */ - + int codec_config; /* flags for codecs or layouts with minor variations; meaning is up to them */ int32_t ws_output_size; /* WS ADPCM: output bytes for this block */ - void * start_vgmstream; /* a copy of the VGMSTREAM as it was at the beginning of the stream (for custom layouts) */ - /* Data the codec needs for the whole stream. This is for codecs too - * different from vgmstream's structure to be reasonably shoehorned into - * using the ch structures. + /* main state */ + VGMSTREAMCHANNEL * ch; /* array of channels */ + VGMSTREAMCHANNEL * start_ch; /* shallow copy of channels as they were at the beginning of the stream (for resets) */ + VGMSTREAMCHANNEL * loop_ch; /* shallow copy of channels as they were at the loop point (for loops) */ + void* start_vgmstream; /* shallow copy of the VGMSTREAM as it was at the beginning of the stream (for resets) */ + + void * mixing_data; /* state for mixing effects */ + + /* Optional data the codec needs for the whole stream. This is for codecs too + * different from vgmstream's structure to be reasonably shoehorned. * Note also that support must be added for resetting, looping and * closing for every codec that uses this, as it will not be handled. */ void * codec_data; /* Same, for special layouts. layout_data + codec_data may exist at the same time. */ void * layout_data; + } VGMSTREAM; #ifdef VGM_USE_VORBIS @@ -881,6 +943,7 @@ typedef struct { int bitstream; ogg_vorbis_streamfile ov_streamfile; + int disable_reordering; /* Xiph reorder channels on output, except for some devs */ } ogg_vorbis_codec_data; @@ -1042,21 +1105,21 @@ typedef struct { #ifdef VGM_USE_G7221 typedef struct { - sample buffer[640]; + sample_t buffer[640]; g7221_handle *handle; } g7221_codec_data; #endif #ifdef VGM_USE_G719 typedef struct { - sample buffer[960]; + sample_t buffer[960]; void *handle; } g719_codec_data; #endif #ifdef VGM_USE_MAIATRAC3PLUS typedef struct { - sample *buffer; + sample_t *buffer; int channels; int samples_discard; void *handle; @@ -1085,31 +1148,23 @@ typedef struct { void *io_config; } acm_codec_data; -#define AIX_BUFFER_SIZE 0x1000 -/* AIXery */ -typedef struct { - sample buffer[AIX_BUFFER_SIZE]; - int segment_count; - int stream_count; - int current_segment; - /* one per segment */ - int32_t *sample_counts; - /* organized like: - * segment1_stream1, segment1_stream2, segment2_stream1, segment2_stream2*/ - VGMSTREAM **adxs; -} aix_codec_data; - -/* for files made of "vertical" segments, one per section of a song (using a complete sub-VGMSTREAM) */ +/* for files made of "continuous" segments, one per section of a song (using a complete sub-VGMSTREAM) */ typedef struct { int segment_count; VGMSTREAM **segments; int current_segment; + sample_t *buffer; + int input_channels; /* internal buffer channels */ + int output_channels; /* resulting channels (after mixing, if applied) */ } segmented_layout_data; -/* for files made of "horizontal" layers, one per group of channels (using a complete sub-VGMSTREAM) */ +/* for files made of "parallel" layers, one per group of channels (using a complete sub-VGMSTREAM) */ typedef struct { int layer_count; VGMSTREAM **layers; + sample_t *buffer; + int input_channels; /* internal buffer channels */ + int output_channels; /* resulting channels (after mixing, if applied) */ } layered_layout_data; /* for compressed NWA */ @@ -1162,6 +1217,9 @@ typedef struct { int64_t skipSamples; // number of start samples that will be skipped (encoder delay), for looping adjustments int streamCount; // number of FFmpeg audio streams + int channel_remap_set; + int channel_remap[32]; /* map of channel > new position */ + /*** internal state ***/ // Intermediate byte buffer uint8_t *sampleBuffer; @@ -1289,7 +1347,7 @@ int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream); * The list disables some common formats that may conflict (.wav, .ogg, etc). */ const char ** vgmstream_get_formats(size_t * size); -/* Force enable/disable internal looping. Should be done before playing anything, +/* Force enable/disable internal looping. Should be done before playing anything (or after reset), * and not all codecs support arbitrary loop values ATM. */ void vgmstream_force_loop(VGMSTREAM* vgmstream, int loop_flag, int loop_start_sample, int loop_end_sample); @@ -1300,9 +1358,12 @@ void vgmstream_set_loop_target(VGMSTREAM* vgmstream, int loop_target); /* vgmstream "private" API */ /* -------------------------------------------------------------------------*/ -/* Allocate memory and setup a VGMSTREAM */ +/* Allocate initial memory for the VGMSTREAM */ VGMSTREAM * allocate_vgmstream(int channel_count, int looped); +/* Prepare the VGMSTREAM's initial state once parsed and ready, but before playing. */ +void setup_vgmstream(VGMSTREAM * vgmstream); + /* Get the number of samples of a single frame (smallest self-contained sample group, 1/N channels) */ 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) */ @@ -1313,7 +1374,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. */ -void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to_do, sample * buffer); +void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to_do, sample_t * buffer); /* Calculate number of consecutive samples to do (taking into account stopping for loop start and end) */ int vgmstream_samples_to_do(int samples_this_block, int samples_per_frame, VGMSTREAM * vgmstream); @@ -1321,11 +1382,11 @@ int vgmstream_samples_to_do(int samples_this_block, int samples_per_frame, VGMST /* Detect loop start and save values, or detect loop end and restore (loop back). Returns 1 if loop was done. */ int vgmstream_do_loop(VGMSTREAM * vgmstream); -/* Open the stream for reading at offset (standarized taking into account layouts, channels and so on). - * returns 0 on failure */ +/* Open the stream for reading at offset (taking into account layouts, channels and so on). + * Returns 0 on failure */ int vgmstream_open_stream(VGMSTREAM * vgmstream, STREAMFILE *streamFile, off_t start_offset); -/* get description info */ +/* Get description info */ const char * get_vgmstream_coding_description(coding_t coding_type); const char * get_vgmstream_layout_description(layout_t layout_type); const char * get_vgmstream_meta_description(meta_t meta_type); diff --git a/Plugins/vgmstream/vgmstream.xcodeproj/project.pbxproj b/Plugins/vgmstream/vgmstream.xcodeproj/project.pbxproj index f866abf21..7ccc6412d 100644 --- a/Plugins/vgmstream/vgmstream.xcodeproj/project.pbxproj +++ b/Plugins/vgmstream/vgmstream.xcodeproj/project.pbxproj @@ -199,6 +199,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, es, ); @@ -313,6 +314,13 @@ GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", + VGM_USE_ATRAC9, + VGM_USE_FFMPEG, + VGM_USE_G719, + VGM_USE_G7221, + VGM_USE_MPEG, + VGM_USE_VORBIS, + __MACOSX__, ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -367,6 +375,15 @@ GCC_C_LANGUAGE_STANDARD = gnu99; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + VGM_USE_ATRAC9, + VGM_USE_FFMPEG, + VGM_USE_G719, + VGM_USE_G7221, + VGM_USE_MPEG, + VGM_USE_VORBIS, + __MACOSX__, + ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES;