Merge pull request #363 from bnnm/txtp-bao-etc

txtp bao etc
This commit is contained in:
Christopher Snowhill 2019-02-18 01:20:47 -08:00 committed by GitHub
commit ae0f1b6175
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1429 additions and 433 deletions

544
cli/txtp_maker.py Normal file
View File

@ -0,0 +1,544 @@
#!/usr/bin/env python3
# ########################################################################### #
# TXTP MAKER
# ########################################################################### #
from __future__ import division
import subprocess
import zlib
import os.path
import os
import re
import sys
import fnmatch
def print_usage(appname):
print("Usage: {} (filename) [options]".format(appname)+"\n"
"\n"
"Creates (filename)_(subsong).txtp for every subsong in (filename).\n"
" (filename) can be a * or *.ext wildcard too (works with dupe filters).\n"
"Works with files with no subsongs (unless filtered) too.\n"
"\n"
"Use -h to print [options]. Examples:\n"
"\n"
"{} bgm.fsb -in -fcm 2 -fms 5.0 ".format(appname)+"\n"
" make TXTP for subsongs with at least 2 channels and 5 seconds\n"
"{} *.scd -r -fd -l 2".format(appname)+"\n"
" all .scd in subdirs, ignoring dupes and making per 2ch layers\n"
"{} *.sm1 -fne .+STREAM[.]SS[0-9]$ ".format(appname)+"\n"
" all .sm1 excluding those subsong names that ends with 'STREAM.SS0..9'\n"
"{} samples.bnk -fni ^bgm.? ".format(appname)+"\n"
" in .bnk including only subsong names that start with 'bgm'\n"
"{} * -r -fss 1".format(appname)+"\n"
" all files in subdirs with at least 1 subsong (ignoring formats without them)\n"
)
def print_help(appname):
print("Options:\n"
" -r: find recursive (writes files to current dir, with dir in TXTP)\n"
" -c (name): set path to CLI (default: test.exe)\n"
" -n (name): use (name)_(subsong).txtp format\n"
" You can put '{filename}' somewhere to get it substituted by the base name\n"
" -z N: zero-fill subsong number (default: auto fill up to total subsongs)\n"
" -d (dir): add dir in TXTP (if the file will reside in a subdir)\n"
" -m: create mini-txtp\n"
" -o: overwrite existing .txtp (beware when using with internal names)\n"
" -in: name TXTP using the subsong's internal name if found\n"
" -ie: remove internal name's extension\n"
" -ii: add subsong number when using internal name\n"
" -l N: create multiple TXTP per subsong layers, every N channels\n"
" -fd: filter duplicates (slower)\n"
" -fcm N: filter min channels\n"
" -fcM N: filter by max channels\n"
" -frm N: filter by min sample rate\n"
" -frM N: filter by max sample rate\n"
" -fsm N.N: filter by min seconds\n"
" -fsM N.N: filter by max seconds\n"
" -fss N: filter min subsongs (1 filters formats incapable of subsongs)\n"
" -fni (regex): filter by subsong name, include files that match\n"
" -fne (regex): filter by subsong name, exclude files that match\n"
" -v (name): verbose level (off|trace|debug|info, default: info)\n"
" -h N: show this help\n"
)
# ########################################################################### #
def find_files(dir, pattern, recursive):
files = []
for root, dirnames, filenames in os.walk(dir):
for filename in fnmatch.filter(filenames, pattern):
files.append(os.path.join(root, filename))
if not recursive:
break
return files
def make_cmd(cfg, fname_in, fname_out, target_subsong):
if (cfg.test_dupes):
cmd = "{} -s {} -i -o {} {}".format(cfg.cli, target_subsong, fname_out, fname_in)
else:
cmd = "{} -s {} -m -i -o {} {}".format(cfg.cli, target_subsong, fname_out, fname_in)
return cmd
class LogHelper(object):
def __init__(self, cfg):
self.cfg = cfg
def trace(self, msg):
v = self.cfg.verbose
if v == "trace":
print(msg)
def debug(self, msg):
v = self.cfg.verbose
if v == "trace" or v == "debug":
print(msg)
def info(self, msg):
v = self.cfg.verbose
if v == "trace" or v == "debug" or v == "info":
print(msg)
class ConfigHelper(object):
show_help = False
cli = "test.exe"
recursive = False
base_name = ''
zero_fill = -1
subdir = ''
mini_txtp = False
overwrite = False
layers = 0
use_internal_name = False
use_internal_ext = False
use_internal_index = False
test_dupes = False
min_channels = 0
max_channels = 0
min_sample_rate = 0
max_sample_rate = 0
min_seconds = 0.0
max_seconds = 0.0
min_subsongs = 0
include_regex = ""
exclude_regex = ""
verbose = "info"
argv_len = 0
index = 0
def read_bool(self, command, default):
if self.index > self.argv_len - 1:
return default
if self.argv[self.index] == command:
val = True
self.index += 1
return val
return default
def read_value(self, command, default):
if self.index > self.argv_len - 2:
return default
if self.argv[self.index] == command:
val = self.argv[self.index+1]
self.index += 2
return val
return default
def read_string(self, command, default):
return str(self.read_value(command, default))
def read_int(self, command, default):
return int(self.read_value(command, default))
def read_float(self, command, default):
return float(self.read_value(command, default))
#todo improve this poop
def __init__(self, argv):
self.index = 2 #after file
self.argv = argv
self.argv_len = len(argv)
if argv[1] == '-h':
self.show_help = True
prev_index = self.index
while self.index < len(self.argv):
self.show_help = self.read_bool('-h', self.show_help)
self.cli = self.read_string('-c', self.cli)
self.recursive = self.read_bool('-r', self.recursive)
self.base_name = self.read_string('-n', self.base_name)
self.zero_fill = self.read_int('-z', self.zero_fill)
self.subdir = self.read_string('-d', self.subdir)
self.test_dupes = self.read_bool('-fd', self.test_dupes)
self.min_channels = self.read_int('-fcm', self.min_channels)
self.max_channels = self.read_int('-fcM', self.max_channels)
self.min_sample_rate = self.read_int('-frm', self.min_sample_rate)
self.max_sample_rate = self.read_int('-frM', self.max_sample_rate)
self.min_seconds = self.read_float('-fsm', self.min_seconds)
self.max_seconds = self.read_float('-fsM', self.max_seconds)
self.min_subsongs = self.read_int('-fss', self.min_subsongs)
self.include_regex = self.read_string('-fni', self.include_regex)
self.exclude_regex = self.read_string('-fne', self.exclude_regex)
self.mini_txtp = self.read_bool('-m', self.mini_txtp)
self.overwrite = self.read_bool('-o', self.overwrite)
self.layers = self.read_int('-l', self.layers)
self.use_internal_name = self.read_bool('-in', self.use_internal_name)
self.use_internal_ext = self.read_bool('-ie', self.use_internal_ext)
self.use_internal_index = self.read_bool('-ii', self.use_internal_index)
self.verbose = self.read_string('-v', self.verbose)
if prev_index == self.index:
self.index += 1
prev_index = self.index
if (self.subdir != '') and not (self.subdir.endswith('/') or self.subdir.endswith('\\')):
self.subdir += '/'
def __str__(self):
return str(self.__dict__)
class Cr32Helper(object):
crc32_map = {}
dupe = False
cfg = None
def __init__(self, cfg):
self.cfg = cfg
def get_crc32(self, fname):
buf_size = 0x8000
with open(fname, 'rb') as file:
buf = file.read(buf_size)
crc32 = 0
while len(buf) > 0:
crc32 = zlib.crc32(buf, crc32)
buf = file.read(buf_size)
return crc32 & 0xFFFFFFFF
def update(self, fname):
cfg = self.cfg
self.dupe = False
if cfg.test_dupes == 0:
return
if not os.path.exists(fname):
return
crc32_str = format(self.get_crc32(fname),'08x')
if (crc32_str in self.crc32_map):
self.dupe = True
return
self.crc32_map[crc32_str] = True
return
def is_dupe(self):
return self.dupe
class TxtpMaker(object):
channels = 0
sample_rate = 0
num_samples = 0
stream_count = 0
stream_index = 0
stream_name = ''
stream_seconds = 0
def __init__(self, cfg, output_b, log):
self.cfg = cfg
self.log = log
self.output = str(output_b).replace("\\r","").replace("\\n","\n")
self.channels = self.get_value("channels: ")
self.sample_rate = self.get_value("sample rate: ")
self.num_samples = self.get_value("stream total samples: ")
self.stream_count = self.get_value("stream count: ")
self.stream_index = self.get_value("stream index: ")
self.stream_name = self.get_string("stream name: ")
if self.channels == 0:
raise ValueError('Incorrect command result')
self.stream_seconds = self.num_samples / self.sample_rate
def __str__(self):
return str(self.__dict__)
def get_string(self, str):
find_pos = self.output.find(str)
if (find_pos == -1):
return ''
cut_pos = find_pos + len(str)
str_cut = self.output[cut_pos:]
return str_cut.split()[0]
def get_value(self, str):
res = self.get_string(str)
if (res == ''):
return 0;
return int(res)
def is_ignorable(self):
cfg = self.cfg
if (self.channels < cfg.min_channels):
return True;
if (cfg.max_channels > 0 and self.channels > cfg.max_channels):
return True;
if (self.sample_rate < cfg.min_sample_rate):
return True;
if (cfg.max_sample_rate > 0 and self.sample_rate > cfg.max_sample_rate):
return True;
if (self.stream_seconds < cfg.min_seconds):
return True;
if (cfg.max_seconds > 0 and self.stream_seconds > cfg.max_seconds):
return True;
if (self.stream_count < cfg.min_subsongs):
return True;
if (cfg.exclude_regex != "" and self.stream_name != ""):
p = re.compile(cfg.exclude_regex)
if (p.match(self.stream_name) != None):
return True
if (cfg.include_regex != "" and self.stream_name != ""):
p = re.compile(cfg.include_regex)
if (p.match(self.stream_name) == None):
return True
return False
def get_stream_mask(self, layer):
cfg = self.cfg
mask = '#c'
loops = cfg.layers
if layer + cfg.layers > self.channels:
loops = self.channels - cfg.layers
for ch in range(0,loops):
mask += str(layer+ch) + ','
mask = mask[:-1]
return mask
def get_stream_name(self):
cfg = self.cfg
if not cfg.use_internal_name:
return ''
txt = self.stream_name
# remove paths #todo maybe config/replace?
pos = txt.rfind("\\")
if (pos != -1):
txt = txt[pos+1:]
pos = txt.rfind("/")
if (pos != -1):
txt = txt[pos+1:]
# remove bad chars
txt = txt.replace("%", "_")
txt = txt.replace("*", "_")
txt = txt.replace("?", "_")
txt = txt.replace(":", "_")
txt = txt.replace("\"", "_")
txt = txt.replace("|", "_")
txt = txt.replace("<", "_")
txt = txt.replace(">", "_")
if not cfg.use_internal_ext:
pos = txt.rfind(".")
if (pos != -1):
txt = txt[:pos]
return txt
def write(self, outname, line):
cfg = self.cfg
outname += '.txtp'
if not cfg.overwrite and os.path.exists(outname):
raise ValueError('TXTP exists in path: ' + outname)
ftxtp = open(outname,"w+")
if line != '':
ftxtp.write(line)
ftxtp.close()
self.log.debug("created: " + outname)
return
def make(self, fname_path, fname_clean):
cfg = self.cfg
total_done = 0
if self.is_ignorable():
return total_done
# write plain (name).txtp when no subsongs
if self.stream_count <= 1:
index = ""
else:
index = str(self.stream_index)
if cfg.zero_fill < 0:
index = index.zfill(len(str(self.stream_count)))
else:
index = index.zfill(cfg.zero_fill)
if cfg.mini_txtp:
outname = fname_path
if index != "":
outname += "#" + index
if cfg.layers > 0 and cfg.layers < self.channels:
for layer in range(0, self.channels, cfg.layers):
mask = self.get_stream_mask(layer)
self.write(outname + mask, '')
total_done += 1
else:
self.write(outname, '')
total_done += 1
else:
stream_name = self.get_stream_name()
if stream_name != '':
outname = stream_name
if cfg.use_internal_index:
outname += "_{}".format(index)
else:
if cfg.base_name != '':
fname_base = os.path.basename(fname_path)
pos = fname_base.rfind(".") #remove ext
if (pos != -1 and pos > 1):
fname_base = fname_base[:pos]
txt = cfg.base_name
txt = txt.replace("{filename}",fname_base)
else:
txt = fname_path
pos = txt.rfind(".") #remove ext
if (pos != -1 and pos > 1):
txt = txt[:pos]
outname = "{}".format(txt)
if index != "":
outname += "_" + index
line = ''
if cfg.subdir != '':
line += cfg.subdir
line += fname_clean
if index != "":
line += "#" + index
if cfg.layers > 0 and cfg.layers < self.channels:
done = 0
for layer in range(0, self.channels, cfg.layers):
sub = chr(ord('a') + done)
done += 1
mask = self.get_stream_mask(layer)
self.write(outname + sub, line + mask)
total_done += 1
else:
self.write(outname, line)
total_done += 1
return total_done
def has_more_subsongs(self, target_subsong):
return target_subsong < self.stream_count
# ########################################################################### #
def main():
appname = os.path.basename(sys.argv[0])
if (len(sys.argv) <= 1):
print_usage(appname)
return
cfg = ConfigHelper(sys.argv)
crc32 = Cr32Helper(cfg)
log = LogHelper(cfg)
if cfg.show_help:
print_help(appname)
return
fname = sys.argv[1]
fnames_in = find_files('.', fname, cfg.recursive)
total_created = 0
total_dupes = 0
total_errors = 0
for fname_in in fnames_in:
fname_in_clean = fname_in.replace("\\", "/")
if fname_in_clean.startswith("./"):
fname_in_clean = fname_in_clean[2:]
fname_in_base = os.path.basename(fname_in)
if fname_in.startswith(".\\"): #skip starting dot for extensionless files
fname_in = fname_in[2:]
fname_out = ".temp." + fname_in_base + ".wav"
created = 0
dupes = 0
errors = 0
target_subsong = 1
while 1:
try:
cmd = make_cmd(cfg, fname_in, fname_out, target_subsong)
log.trace("calling: " + cmd)
output_b = subprocess.check_output(cmd, shell=False) #stderr=subprocess.STDOUT
except subprocess.CalledProcessError as e:
log.debug("ignoring CLI error in " + fname_in + "#"+str(target_subsong)+": " + e.output)
errors += 1
break
if target_subsong == 1:
log.debug("processing {}...".format(fname_in_clean))
maker = TxtpMaker(cfg, output_b, log)
if not maker.is_ignorable():
crc32.update(fname_out)
if not crc32.is_dupe():
created += maker.make(fname_in_base, fname_in_clean)
else:
dupes += 1
log.debug("Dupe subsong {}".format(target_subsong))
if not maker.has_more_subsongs(target_subsong):
break
target_subsong += 1
if target_subsong % 200 == 0:
log.info("{}/{} subsongs... ".format(target_subsong, maker.stream_count) +
"({} dupes, {} errors)".format(dupes, errors)
)
if os.path.exists(fname_out):
os.remove(fname_out)
total_created += created
total_dupes += dupes
total_errors += errors
log.info("done! ({} done, {} dupes, {} errors)".format(total_created, total_dupes, total_errors))
if __name__ == "__main__":
main()

View File

@ -216,6 +216,7 @@ fail:
}
static void print_info(VGMSTREAM * vgmstream, cli_config *cfg) {
int channels = vgmstream->channels;
if (!cfg->play_sdtout) {
if (cfg->print_adxencd) {
printf("adxencd");
@ -236,7 +237,7 @@ static void print_info(VGMSTREAM * vgmstream, cli_config *cfg) {
else if (cfg->print_batchvar) {
if (!cfg->print_metaonly)
printf("set fname=\"%s\"\n",cfg->outfilename);
printf("set tsamp=%d\nset chan=%d\n", vgmstream->num_samples, vgmstream->channels);
printf("set tsamp=%d\nset chan=%d\n", vgmstream->num_samples, channels);
if (vgmstream->loop_flag)
printf("set lstart=%d\nset lend=%d\nset loop=1\n", vgmstream->loop_start_sample, vgmstream->loop_end_sample);
else
@ -312,16 +313,18 @@ static void apply_config(VGMSTREAM * vgmstream, cli_config *cfg) {
}
}
void apply_fade(sample * buf, VGMSTREAM * vgmstream, int to_get, int i, int len_samples, int fade_samples) {
if (vgmstream->loop_flag && fade_samples > 0) {
void apply_fade(sample * buf, VGMSTREAM * vgmstream, int to_get, int i, int len_samples, int fade_samples, int channels) {
int is_fade_on = vgmstream->loop_flag;
if (is_fade_on && fade_samples > 0) {
int samples_into_fade = i - (len_samples - fade_samples);
if (samples_into_fade + to_get > 0) {
int j, k;
for (j = 0; j < to_get; j++, samples_into_fade++) {
if (samples_into_fade > 0) {
double fadedness = (double)(fade_samples - samples_into_fade) / fade_samples;
for (k = 0; k < vgmstream->channels; k++) {
buf[j*vgmstream->channels+k] = (sample)buf[j*vgmstream->channels+k]*fadedness;
for (k = 0; k < channels; k++) {
buf[j*channels + k] = (sample)buf[j*channels + k] * fadedness;
}
}
}
@ -337,6 +340,7 @@ int main(int argc, char ** argv) {
char outfilename_temp[PATH_LIMIT];
sample * buf = NULL;
int channels;
int32_t len_samples;
int32_t fade_samples;
int i, j;
@ -453,20 +457,22 @@ int main(int argc, char ** argv) {
/* last init */
buf = malloc(BUFFER_SAMPLES*sizeof(sample)*vgmstream->channels);
channels = vgmstream->channels;
buf = malloc(BUFFER_SAMPLES * sizeof(sample) * channels);
if (!buf) {
fprintf(stderr,"failed allocating output buffer\n");
goto fail;;
goto fail;
}
/* slap on a .wav header */
{
uint8_t wav_buf[0x100];
int channels = (cfg.only_stereo != -1) ? 2 : vgmstream->channels;
int channels_write = (cfg.only_stereo != -1) ? 2 : channels;
size_t bytes_done;
bytes_done = make_wav_header(wav_buf,0x100,
len_samples, vgmstream->sample_rate, channels,
len_samples, vgmstream->sample_rate, channels_write,
cfg.write_lwav, cfg.lwav_loop_start, cfg.lwav_loop_end);
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
@ -477,15 +483,15 @@ int main(int argc, char ** argv) {
while (cfg.play_forever) {
int to_get = BUFFER_SAMPLES;
render_vgmstream(buf,to_get,vgmstream);
render_vgmstream(buf, to_get, vgmstream);
swap_samples_le(buf,vgmstream->channels*to_get); /* write PC endian */
swap_samples_le(buf, channels * to_get); /* write PC endian */
if (cfg.only_stereo != -1) {
for (j = 0; j < to_get; j++) {
fwrite(buf+j*vgmstream->channels+(cfg.only_stereo*2),sizeof(sample),2,outfile);
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample), 2, outfile);
}
} else {
fwrite(buf,sizeof(sample)*vgmstream->channels,to_get,outfile);
fwrite(buf, sizeof(sample) * channels, to_get, outfile);
}
}
@ -496,17 +502,17 @@ int main(int argc, char ** argv) {
if (i + BUFFER_SAMPLES > len_samples)
to_get = len_samples - i;
render_vgmstream(buf,to_get,vgmstream);
render_vgmstream(buf, to_get, vgmstream);
apply_fade(buf, vgmstream, to_get, i, len_samples, fade_samples);
apply_fade(buf, vgmstream, to_get, i, len_samples, fade_samples, channels);
swap_samples_le(buf,vgmstream->channels*to_get); /* write PC endian */
swap_samples_le(buf, channels * to_get); /* write PC endian */
if (cfg.only_stereo != -1) {
for (j = 0; j < to_get; j++) {
fwrite(buf+j*vgmstream->channels+(cfg.only_stereo*2),sizeof(sample),2,outfile);
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample), 2, outfile);
}
} else {
fwrite(buf,sizeof(sample)*vgmstream->channels,to_get,outfile);
fwrite(buf, sizeof(sample) * channels, to_get, outfile);
}
}
@ -535,11 +541,11 @@ int main(int argc, char ** argv) {
/* slap on a .wav header */
{
uint8_t wav_buf[0x100];
int channels = (cfg.only_stereo != -1) ? 2 : vgmstream->channels;
int channels_write = (cfg.only_stereo != -1) ? 2 : channels;
size_t bytes_done;
bytes_done = make_wav_header(wav_buf,0x100,
len_samples, vgmstream->sample_rate, channels,
len_samples, vgmstream->sample_rate, channels_write,
cfg.write_lwav, cfg.lwav_loop_start, cfg.lwav_loop_end);
fwrite(wav_buf,sizeof(uint8_t),bytes_done,outfile);
@ -551,17 +557,17 @@ int main(int argc, char ** argv) {
if (i + BUFFER_SAMPLES > len_samples)
to_get = len_samples - i;
render_vgmstream(buf,to_get,vgmstream);
render_vgmstream(buf, to_get, vgmstream);
apply_fade(buf, vgmstream, to_get, i, len_samples, fade_samples);
apply_fade(buf, vgmstream, to_get, i, len_samples, fade_samples, channels);
swap_samples_le(buf,vgmstream->channels*to_get); /* write PC endian */
swap_samples_le(buf, channels * to_get); /* write PC endian */
if (cfg.only_stereo != -1) {
for (j = 0; j < to_get; j++) {
fwrite(buf+j*vgmstream->channels+(cfg.only_stereo*2),sizeof(sample),2,outfile);
fwrite(buf + j*channels + (cfg.only_stereo*2), sizeof(sample), 2, outfile);
}
} else {
fwrite(buf,sizeof(sample)*vgmstream->channels,to_get,outfile);
fwrite(buf, sizeof(sample) * channels, to_get, outfile);
}
}
fclose(outfile);
@ -574,14 +580,12 @@ int main(int argc, char ** argv) {
return EXIT_SUCCESS;
fail:
if (!cfg.play_sdtout)
{
if (!cfg.play_sdtout) {
if (outfile != NULL)
{
fclose(outfile);
}
}
close_vgmstream(vgmstream);
free(buf);
return EXIT_FAILURE;
}

View File

@ -5,29 +5,73 @@ TXTP is a text file with commands, to improve support for games using audio in c
Simply create a file named `(filename).txtp`, and inside write the commands described below.
## TXTP features
## TXTP FEATURES
### Play separate intro + loop files together as a single track
- __Ratchet & Clank (PS2)__: _bgm01.txtp_
Some games clumsily loop audio by using multiple full file "segments":
__Ratchet & Clank (PS2)__: _bgm01.txtp_
```
# define several files to play as one (there is no limit)
# define 2 or more segments to play as one
BGM01_BEGIN.VAG
BGM01_LOOPED.VAG
# multi-files must define loops
# segments must define loops
loop_start_segment = 2 # 2nd file start
loop_end_segment = 2 # optional, default is last
```
Channel number must be equal, mixing sample rates is ok (uses first).
#channel number must be equal, mixing sample rates is ok (uses first)
If your loop segment has proper loops you want to keep, you can use:
```
BGM_SUMMON_0001_01-Intro.hca
BGM_SUMMON_0001_01-Intro2.hca
BGM_SUMMON_0001_01.hca
loop_start_segment = 3
loop_mode = keep # loops in 3rd file's loop_start to 3rd file's loop_end
```
```
bgm_intro.adx
bgm_main.adx
bgm_main2.adx
loop_start_segment = 2
loop_end_segment = 3
loop_mode = keep # loops in 2nd file's loop_start to 3rd file's loop_end
```
### Multilayered songs
TXTP "layers" play songs with channels/parts divided into files as one (for example main melody + vocal track).
__Nier Automata__: _BGM_0_012_song2.txtp_
```
# mix dynamic sections (2ch * 2)
BGM_0_012_04.wem
BGM_0_012_07.wem
mode = layers
```
__Life is Strange__: _BIK_E1_6A_DialEnd.txtp_
```
# bik multichannel isn't autodetectable so must mix manually (1ch * 3)
BIK_E1_6A_DialEnd_00000000.audio.multi.bik#1
BIK_E1_6A_DialEnd_00000000.audio.multi.bik#2
BIK_E1_6A_DialEnd_00000000.audio.multi.bik#3
mode = layers
```
Note that the number of channels is the sum of all layers, so three 2ch layers play as a 6ch file.
### Minifiles for bank formats without splitters
- __Super Robot Taisen OG Saga - Masou Kishin III - Pride of Justice (Vita)__: _bgm_12.txtp_
__Super Robot Taisen OG Saga - Masou Kishin III - Pride of Justice (Vita)__: _bgm_12.txtp_
```
# select subsong 12
bigfiles/bgm.sxd2#12 #relative paths are ok too for TXTP
#bigfiles/bgm.sxd2#s12 # "sN" is al alt for subsong
#bigfiles/bgm.sxd2#s12 # "sN" is alt for subsong
# single files loop normally by default
# if loop segment is defined it forces a full loop (0..num_samples)
@ -35,7 +79,7 @@ bigfiles/bgm.sxd2#12 #relative paths are ok too for TXTP
```
### Play segmented subsongs as one
- __Prince of Persia Sands of Time__: _song_01.txtp_
__Prince of Persia Sands of Time__: _song_01.txtp_
```
# can use ranges ~ to avoid so much C&P
amb_fx.sb0#254
@ -48,13 +92,13 @@ loop_start_segment = 3
### Channel mask for channel subsongs/layers
- __Final Fantasy XIII-2__: _music_Home_01.ps3.txtp_
__Final Fantasy XIII-2__: _music_Home_01.ps3.txtp_
```
#plays channels 1 and 2 = 1st subsong
music_Home.ps3.scd#c1,2
```
- __Final Fantasy XIII-2__: _music_Home_02.ps3.txtp_
__Final Fantasy XIII-2__: _music_Home_02.ps3.txtp_
```
#plays channels 3 and 4 = 2nd subsong
music_Home.ps3.scd#c3,4
@ -63,48 +107,10 @@ music_Home.ps3.scd#c3,4
```
### Multilayered songs
TXTP "layers" play songs with channels/parts divided into files as one.
- __Nier Automata__: _BGM_0_012_song2.txtp_
```
# mix dynamic sections (2ch * 2)
BGM_0_012_04.wem
BGM_0_012_07.wem
mode = layers
```
- __Life is Strange__: _BIK_E1_6A_DialEnd.txtp_
```
# bik multichannel isn't autodetectable so must mix manually (1ch * 3)
BIK_E1_6A_DialEnd_00000000.audio.multi.bik#1
BIK_E1_6A_DialEnd_00000000.audio.multi.bik#2
BIK_E1_6A_DialEnd_00000000.audio.multi.bik#3
mode = layers
```
Note that the number of channels is the sum of all layers, so three 2ch layers play as a 6ch file.
### Channel swapping/mapping
TXTP can swap channels for custom channel mappings. It does "swapping" rather than simpler "mapping" since vgmstream can't read a format's mappings or guess which channel is which. Format is:
```
#ch1 = first
file1.ext#m2-3 # "FL BL FR BR" to "FL FR BL BR"
#do note the order specified affects swapping
file2.ext#m2-3,4-5,4-6 # ogg "FL CN FR BL BR SB" to wav "FL FR CN SB BL BR"
```
Note that channel masking applies after mappings.
### Custom play settings
Those setting should override player's defaults if set (except "loop forever"). They are equivalent to some test.exe options.
- __God Hand (PS2)__: _boss2_3ningumi_ver6.txtp_ (each line is a separate TXTP)
__God Hand (PS2)__: _boss2_3ningumi_ver6.txtp_ (each line is a separate TXTP)
```
# set number of loops
boss2_3ningumi_ver6.adx#l3
@ -136,6 +142,19 @@ boss2_3ningumi_ver6.adx#l1.5#d1#f5
For segments and layers the first file defines looping options.
### Force sample rate
A few games set a sample rate value in the header but actually play with other (applying some of pitch or just forcing it)
__Super Paper Mario (Wii)__
```
btl_koopa1_44k_lp.brstm#h22050 #in hz
```
__Patapon (PSP)__
```
ptp_btl_bgm_voice.sgd#s1#h11050
```
### Force plugin extensions
vgmstream supports a few common extensions that confuse plugins, like .wav/ogg/aac/opus/etc, so for them those extensions are disabled and are expected to be renamed to .lwav/logg/laac/lopus/etc. TXTP can make plugins play those disabled extensions, since it calls files directly by filename.
@ -175,7 +194,7 @@ commands = #s12
```
## TXTP parsing issues
## TXTP PARSING ISSUES
*Commands* can be chained, but must not be separated by a space (everything after space may be ignored):
```
bgm bank.sxd2#s12#c1,2 #spaces + comment after commands is ignored
@ -193,34 +212,14 @@ loop_start_segment = 1 #spaces surrounding value are ignored
```
```
bgm.sxd2
config = #s12#c1,2 #must not have spaces once value starts until end
commands = #s12#c1,2 #must not have spaces once value starts until end
```
The parser is very simplistic and fairly lax, though may be erratic with edge cases or behave unexpectedly due to unforeseen use-cases and bugs. As filenames may contain spaces or #, certain name patterns could fool it too. Keep in mind this while making .txtp files.
## Mini TXTP
## MINI-TXTP
To simplify TXTP creation, if the .txtp is empty (0 bytes) its filename is used directly as a command. Note that extension is also included (since vgmstream needs a full filename).
- _bgm.sxd2#12.txtp_: plays subsong 12
- _Ryoshima Coast 1 & 2.aix#c1,2.txtp_: channel mask
- _boss2_3ningumi_ver6.adx#l2#F.txtp_: loop twice then play song end file normally
- etc
## Other examples
_Join "segments" (intro+body):_
```
#files must have same number of channels
Song001_intro.ogg
Song001_body.ogg
loop_start_segment = 2
```
_Join "layers" (ex. main+vocals):_
```
#files must have same number of samples
Song001_main.ogg
Song001_vocals.ogg
mode = layers
```

View File

@ -131,11 +131,11 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal
int length_in_ms=0, channels = 0, samplerate = 0;
int total_samples = -1;
int bitrate = 0;
int loop_start = -1, loop_end = -1;
int loop_flag = -1, loop_start = -1, loop_end = -1;
pfc::string8 description;
pfc::string8_fast temp;
get_subsong_info(p_subsong, temp, &length_in_ms, &total_samples, &loop_start, &loop_end, &samplerate, &channels, &bitrate, description, p_abort);
get_subsong_info(p_subsong, temp, &length_in_ms, &total_samples, &loop_flag, &loop_start, &loop_end, &samplerate, &channels, &bitrate, description, p_abort);
/* set tag info (metadata tab in file properties) */
@ -193,7 +193,8 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal
p_info.info_set_bitrate(bitrate / 1000);
if (total_samples > 0)
p_info.info_set_int("stream_total_samples", total_samples);
if (loop_start >= 0 && loop_end >= loop_start) {
if (loop_start >= 0 && loop_end > loop_start) {
p_info.info_set("looping", loop_flag > 0 ? "enabled" : "disabled");
p_info.info_set_int("loop_start", loop_start);
p_info.info_set_int("loop_end", loop_end);
}
@ -440,7 +441,7 @@ void input_vgmstream::setup_vgmstream(abort_callback & p_abort) {
fade_samples = (int)(config.song_fade_time * vgmstream->sample_rate);
}
void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort) {
void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_flag, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort) {
VGMSTREAM * infostream = NULL;
bool is_infostream = false;
foobar_song_config infoconfig;
@ -473,10 +474,9 @@ void input_vgmstream::get_subsong_info(t_uint32 p_subsong, pfc::string_base & ti
*channels = infostream->channels;
*total_samples = infostream->num_samples;
*bitrate = get_vgmstream_average_bitrate(infostream);
if (infostream->loop_flag) {
*loop_start = infostream->loop_start_sample;
*loop_end = infostream->loop_end_sample;
}
*loop_flag = infostream->loop_flag;
*loop_start = infostream->loop_start_sample;
*loop_end = infostream->loop_end_sample;
char temp[1024];
describe_vgmstream(infostream, temp, 1024);

View File

@ -85,7 +85,7 @@ class input_vgmstream : public input_stubs {
VGMSTREAM * init_vgmstream_foo(t_uint32 p_subsong, const char * const filename, abort_callback & p_abort);
void setup_vgmstream(abort_callback & p_abort);
void load_settings();
void get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort);
void get_subsong_info(t_uint32 p_subsong, pfc::string_base & title, int *length_in_ms, int *total_samples, int *loop_flag, int *loop_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort);
bool get_description_tag(pfc::string_base & temp, pfc::string_base const& description, const char *tag, char delimiter = '\n');
void set_config_defaults(foobar_song_config *current);
void apply_config(VGMSTREAM * vgmstream, foobar_song_config *current);

View File

@ -509,42 +509,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;
flush_mpeg(data);
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 +548,17 @@ void seek_mpeg(VGMSTREAM *vgmstream, int32_t num_sample) {
}
else {
int i;
/* re-start from 0 */
/* 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 */

View File

@ -123,6 +123,7 @@ static const char* extension_list[] = {
"de2",
"dec",
"dmsg",
"ds2", //txth/reserved [Star Wars Bounty Hunter (GC)]
"dsf",
"dsp",
"dspw",
@ -1170,6 +1171,7 @@ 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"},
};

View File

@ -96,11 +96,9 @@ int setup_layout_layered(layered_layout_data* data) {
}
}
//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 */
}
return 1;

View File

@ -6,7 +6,7 @@
* 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;
int samples_written = 0, loop_samples_skip = 0;
segmented_layout_data *data = vgmstream->layout_data;
@ -16,28 +16,34 @@ void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM
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;
}
@ -46,6 +52,12 @@ void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM
if (samples_to_do > sample_count - samples_written)
samples_to_do = sample_count - samples_written;
/* 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) {
data->current_segment++;
@ -57,9 +69,15 @@ void render_vgmstream_segmented(sample * buffer, int32_t sample_count, VGMSTREAM
render_vgmstream(&buffer[samples_written*data->segments[data->current_segment]->channels],
samples_to_do,data->segments[data->current_segment]);
samples_written += samples_to_do;
vgmstream->current_sample += samples_to_do;
vgmstream->samples_into_block += samples_to_do;
if (loop_samples_skip > 0) {
loop_samples_skip -= samples_to_do;
vgmstream->samples_into_block += samples_to_do;
}
else {
samples_written += samples_to_do;
vgmstream->current_sample += samples_to_do;
vgmstream->samples_into_block += samples_to_do;
}
}
}
@ -116,9 +134,7 @@ int setup_layout_segmented(segmented_layout_data* data) {
}
/* 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 */
}

View File

@ -136,8 +136,8 @@ VGMSTREAM * init_vgmstream_aix(STREAMFILE *streamFile) {
/* 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));
setup_vgmstream(temp_vgmstream); /* final setup as the VGMSTREAM was created manually */
}
}

View File

@ -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,63 @@ 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);
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);
channel_count = read_8bit(header_offset+0x4b, streamFile);
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) */

View File

@ -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;

View File

@ -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);

View File

@ -223,7 +223,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;
@ -517,14 +517,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 +1195,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;
}

View File

@ -4,6 +4,9 @@
#define TXT_LINE_MAX 0x2000
#ifdef VGMSTREAM_MIXING
#define TXTP_MIXING_MAX 64
#endif
typedef struct {
char filename[TXT_LINE_MAX];
@ -12,12 +15,20 @@ typedef struct {
int channel_mappings_on;
int channel_mappings[32];
#if VGMSTREAM_MIXING
int mixing_count;
mix_config_data mixing[TXTP_MIXING_MAX];
#endif
double config_loop_count;
double config_fade_time;
double config_fade_delay;
int config_ignore_loop;
int config_force_loop;
int config_ignore_fade;
int sample_rate;
} txtp_entry;
typedef struct {
@ -32,6 +43,7 @@ typedef struct {
int default_entry_set;
size_t is_layered;
int is_loop_keep;
} txtp_header;
static txtp_header* parse_txtp(STREAMFILE* streamFile);
@ -148,17 +160,24 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
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;
if (txtp->is_loop_keep /*&& data_s->segments[i]->loop_start_sample*/)
loop_start_sample = num_samples + data_s->segments[i]->loop_start_sample;
else
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;
if (txtp->is_loop_keep && data_s->segments[i]->loop_end_sample)
loop_end_sample = num_samples - data_s->segments[i]->num_samples + data_s->segments[i]->loop_end_sample;
else
loop_end_sample = num_samples;
}
}
@ -208,6 +227,51 @@ static void apply_config(VGMSTREAM *vgmstream, txtp_entry *current) {
}
}
#ifdef VGMSTREAM_MIXING
if (current->mixing_count > 0) {
int i, ch_max_cur;
if (vgmstream->mixing_count + current->mixing_count > vgmstream->mixing_size) {
VGM_LOG("TXTP: ignored mixing\n");
return;
}
ch_max_cur = vgmstream->channels;
for (i = 0; i < current->mixing_count; i++) {
mix_config_data mix = current->mixing[i];
vgmstream->mixing[vgmstream->mixing_count] = mix;
vgmstream->mixing_count++;
/* some mixes change output channels */
switch(vgmstream->mixing[i].command) {
case MIX_DOWNMIX:
if (mix.ch_a < 0 || mix.ch_a >= ch_max_cur || ch_max_cur - 1 == 0) break;
ch_max_cur--;
break;
case MIX_DOWNMIX_REST:
if (mix.ch_a < 0 || mix.ch_a >= ch_max_cur) break;
ch_max_cur = mix.ch_a + 1; /* simply clamp channels */
break;
case MIX_UPMIX:
if (mix.ch_a < 0 || mix.ch_a > ch_max_cur) break; /* ch_a can be == max_cur, since we are inserting */
ch_max_cur++;
break;
default:
break;
}
}
vgmstream->output_channels = ch_max_cur;
}
#endif
if (current->sample_rate > 0)
vgmstream->sample_rate = current->sample_rate;
vgmstream->config_loop_count = current->config_loop_count;
vgmstream->config_fade_time = current->config_fade_time;
vgmstream->config_fade_delay = current->config_fade_delay;
@ -238,12 +302,40 @@ static void clean_filename(char * filename) {
}
static void get_double(const char * config, double *value) {
static int get_double(const char * config, double *value) {
int n;
if (sscanf(config, "%lf%n", value,&n) != 1) {
*value = 0;
return 0;
}
return n;
}
static int get_int(const char * config, int *value) {
int n;
if (sscanf(config, "%i%n", value,&n) != 1) {
*value = 0;
return 0;
}
return n;
}
#ifdef VGMSTREAM_MIXING
static void add_mixing(txtp_entry* cfg, mix_config_data* mix, mix_command_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 becode -1 and ignored in code) */
mix->ch_a--;
mix->ch_b--;
mix->command = command;
cfg->mixing[cfg->mixing_count] = *mix; /* memcpy'ed */
cfg->mixing_count++;
}
#endif
static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filename) {
strcpy(current->filename, filename);
@ -260,12 +352,27 @@ static void add_config(txtp_entry* current, txtp_entry* cfg, const char* filenam
}
}
#ifdef VGMSTREAM_MIXING
//*current = *cfg; /* don't memcopy to allow list additions */
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++;
}
}
#endif
current->config_loop_count = cfg->config_loop_count;
current->config_fade_time = cfg->config_fade_time;
current->config_fade_delay = cfg->config_fade_delay;
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;
}
static int add_filename(txtp_header * txtp, char *filename, int is_default) {
@ -349,6 +456,76 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) {
}
}
}
#ifdef VGMSTREAM_MIXING
else if (config[0] == 'm') {
/* channel mixing: file.ext#m(sub-command),(sub-command),etc */
char cmd;
config++;
while (config[0] != '\0') {
mix_config_data mix = {0};
if (config[0]== ',') {
config++;
continue;
}
if (sscanf(config, "%d-%d%n", &mix.ch_a, &mix.ch_b, &n) == 2 && n != 0) {
add_mixing(&cfg, &mix, MIX_SWAP); /* N-M: swaps M with N */
config += n;
continue;
}
if ((sscanf(config, "%d+%d*%f%n", &mix.ch_a, &mix.ch_b, &mix.vol_a, &n) == 3 && n != 0) ||
(sscanf(config, "%d+%dx%f%n", &mix.ch_a, &mix.ch_b, &mix.vol_a, &n) == 3 && n != 0)) {
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_a, &mix.ch_b, &n) == 2 && n != 0) {
add_mixing(&cfg, &mix, MIX_ADD); /* N+M: mixes M to N */
config += n;
continue;
}
if ((sscanf(config, "%d*%f~%f@%f~%f%n", &mix.ch_a, &mix.vol_a, &mix.vol_b, &mix.pos_a, &mix.pos_b, &n) == 5 && n != 0) ||
(sscanf(config, "%dx%f~%f@%f~%f%n", &mix.ch_a, &mix.vol_a, &mix.vol_b, &mix.pos_a, &mix.pos_b, &n) == 5 && n != 0)) {
add_mixing(&cfg, &mix, MIX_CROSSFADE); /* N*V1~V2@P1~P2: fades from volume1 to 2 between position1 to 2 */
config += n;
continue;
}
if ((sscanf(config, "%d*%f%n", &mix.ch_a, &mix.vol_a, &n) == 2 && n != 0) ||
(sscanf(config, "%dx%f%n", &mix.ch_a, &mix.vol_a, &n) == 2 && n != 0)) {
add_mixing(&cfg, &mix, MIX_VOLUME); /* N*V: changes volume of N */
config += n;
continue;
}
if (sscanf(config, "%d%c%n", &mix.ch_a, &cmd, &n) == 2 && n != 0 && cmd == 'D') {
add_mixing(&cfg, &mix, MIX_DOWNMIX_REST); /* ND: downmix N and all following channels */
config += n;
continue;
}
if (sscanf(config, "%d%c%n", &mix.ch_a, &cmd, &n) == 2 && n != 0 && cmd == 'd') {
add_mixing(&cfg, &mix, MIX_DOWNMIX);/* Nd: downmix N only */
config += n;
continue;
}
if (sscanf(config, "%d%c%n", &mix.ch_a, &cmd, &n) == 2 && n != 0 && cmd == 'u') {
add_mixing(&cfg, &mix, MIX_UPMIX); /* Nu: upmix N */
config += n;
continue;
}
break; /* unknown/mix end */
}
}
#endif
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;
@ -385,15 +562,19 @@ static int add_filename(txtp_header * txtp, char *filename, int is_default) {
}
else if (config[0] == 'l') {
config++;
get_double(config, &cfg.config_loop_count);
config += get_double(config, &cfg.config_loop_count);
}
else if (config[0] == 'f') {
config++;
get_double(config, &cfg.config_fade_time);
config += get_double(config, &cfg.config_fade_time);
}
else if (config[0] == 'd') {
config++;
get_double(config, &cfg.config_fade_delay);
config += get_double(config, &cfg.config_fade_delay);
}
else if (config[0] == 'h') {
config++;
config += get_int(config, &cfg.sample_rate);
}
else if (config[0] == ' ') {
continue; /* likely a comment, find next # */
@ -475,6 +656,14 @@ static int parse_keyval(txtp_header * txtp, const char * key, const char * val)
goto fail;
}
}
else if (0==strcmp(key,"loop_mode")) {
if (0==strcmp(val,"keep")) {
txtp->is_loop_keep = 1;
}
else {
goto fail;
}
}
else if (0==strcmp(key,"commands")) {
char val2[TXT_LINE_MAX];
strcpy(val2, val); /* copy since val is modified here but probably not important */

View File

@ -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;
@ -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;
@ -111,7 +113,7 @@ typedef struct {
int sequence_loop;
int sequence_single;
//float duration;
float duration;
char resource_name[255];
@ -201,12 +203,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.
@ -423,7 +425,7 @@ static VGMSTREAM * init_vgmstream_ubi_bao_audio(ubi_bao_header * bao, STREAMFILE
streamData = setup_bao_streamfile(bao, streamFile);
if (!streamData) goto fail;
//dump_streamfile(streamData, "test.out");
vgmstream = init_vgmstream_ubi_bao_base(bao, streamFile, streamData);
if (!vgmstream) goto fail;
@ -576,6 +578,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 +608,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 +690,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 +712,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 +800,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);
@ -925,7 +996,7 @@ static int parse_type_layer(ubi_bao_header * bao, off_t offset, STREAMFILE* stre
bao->num_samples = read_32bit(table_offset + bao->cfg.layer_num_samples, streamFile);
for (i = 0; i < bao->layer_count; i++) {
int channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile);
//int channels = read_32bit(table_offset + bao->cfg.layer_channels, streamFile);
int sample_rate = read_32bit(table_offset + bao->cfg.layer_sample_rate, streamFile);
int stream_type = read_32bit(table_offset + bao->cfg.layer_stream_type, streamFile);
int num_samples = read_32bit(table_offset + bao->cfg.layer_num_samples, streamFile);
@ -941,7 +1012,7 @@ static int parse_type_layer(ubi_bao_header * bao, off_t offset, STREAMFILE* stre
}
/* 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);
//;VGM_ASSERT_ONCE(bao->channels != channels, "UBI BAO: layer channels don't match at %x\n", (uint32_t)table_offset);
/* can be +-1 */
if (bao->num_samples != num_samples && bao->num_samples + 1 == num_samples) {
@ -956,10 +1027,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 */
@ -1015,7 +1115,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 +1136,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 +1254,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 +1282,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;
@ -1501,7 +1611,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 +1625,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.
@ -1532,7 +1648,7 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) {
* - 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?)
* Class 1/2/3 are roughly equivalent to Ubi SB's section1/2/3, and class 4 is
* basically .spN project files.
*
@ -1581,7 +1697,7 @@ 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);
@ -1593,6 +1709,8 @@ static int config_bao_version(ubi_bao_header * bao, STREAMFILE *streamFile) {
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 +1724,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 +1734,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 +1748,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 +1762,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 +1783,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;
@ -1694,6 +1821,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

View File

@ -89,7 +89,7 @@ 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) */
@ -145,7 +145,7 @@ typedef struct {
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 */
@ -1115,13 +1115,13 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream
sb->num_samples = read_32bit(table_offset + sb->cfg.layer_num_samples, streamFile);
for (i = 0; i < sb->layer_count; i++) {
int 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);
//int 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);
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) {
if (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) {
@ -1132,6 +1132,9 @@ static int parse_type_layer(ubi_sb_header * sb, off_t offset, STREAMFILE* stream
goto fail;
}
/* unusual but happens, layers handle it fine [Brothers in Arms 2 (PS2) ex. MP_B01_NL.SB1] */
//;VGM_ASSERT_ONCE(sb->channels != channels, "UBI SB: layer channels don't match at %x\n", (uint32_t)table_offset);
/* can be +-1 */
if (sb->num_samples != num_samples && sb->num_samples + 1 == num_samples) {
sb->num_samples -= 1;
@ -1781,8 +1784,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)
@ -2547,6 +2550,7 @@ 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;
config_sb_audio_fs(sb, 0x28, 0x30, 0x34);
config_sb_audio_he(sb, 0x44, 0x4c, 0x54, 0x5c, 0x64, 0x68);

View File

@ -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);

View File

@ -13,7 +13,7 @@
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,
@ -465,6 +465,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_gin,
init_vgmstream_dsf,
init_vgmstream_208,
init_vgmstream_dsp_ds2,
/* 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 */
@ -487,7 +488,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)
@ -506,14 +507,17 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) {
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 +526,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 */
@ -540,15 +550,14 @@ static VGMSTREAM * init_vgmstream_internal(STREAMFILE *streamFile) {
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 +566,16 @@ 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,92 +591,89 @@ 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 and layout data */
#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);
}
@ -668,137 +684,140 @@ void reset_vgmstream(VGMSTREAM * vgmstream) {
}
if (vgmstream->layout_type==layout_aix) {
if (vgmstream->layout_type == layout_aix) {
aix_codec_data *data = vgmstream->codec_data;
int i;
data->current_segment = 0;
for (i=0;i<data->segment_count*data->stream_count;i++) {
for (i = 0; i < data->segment_count*data->stream_count; i++) {
reset_vgmstream(data->adxs[i]);
}
}
if (vgmstream->layout_type==layout_segmented) {
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
* (ch's streamfiles init in metas, nor their internal state) */
}
/* 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 */
/* up to ~16-24 aren't too rare for multilayered files, more is probably a bug */
if (channel_count <= 0 || channel_count > 64) {
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;
#ifdef VGMSTREAM_MIXING
/* fixed arrays, for now */
vgmstream->mixing_size = 64;
vgmstream->stream_name_size = STREAM_NAME_SIZE;
#endif
return vgmstream;
fail:
if (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 +858,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,13 +874,14 @@ void close_vgmstream(VGMSTREAM * vgmstream) {
}
if (vgmstream->layout_type==layout_aix) {
/* free custom layouts */
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;i<data->segment_count*data->stream_count;i++) {
for (i = 0; i < data->segment_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]);
@ -877,12 +897,12 @@ void close_vgmstream(VGMSTREAM * vgmstream) {
vgmstream->codec_data = NULL;
}
if (vgmstream->layout_type==layout_segmented) {
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 +912,13 @@ void close_vgmstream(VGMSTREAM * vgmstream) {
{
int i,j;
for (i=0;i<vgmstream->channels;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;j<vgmstream->channels;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 +927,10 @@ 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);
free(vgmstream->ch);
free(vgmstream->start_ch);
free(vgmstream->loop_ch);
free(vgmstream->start_vgmstream);
free(vgmstream);
}
@ -947,11 +963,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 +974,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 +988,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) {
@ -2102,22 +2125,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 +2156,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 +2168,7 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) {
vgmstream->coding_type == coding_PSX ||
vgmstream->coding_type == coding_PSX_badflags) {
int i;
for (i=0;i<vgmstream->channels;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 +2179,60 @@ int vgmstream_do_loop(VGMSTREAM * vgmstream) {
/* prepare certain codecs' internal state for looping */
if (vgmstream->coding_type==coding_CRI_HCA) {
if (vgmstream->coding_type == coding_CRI_HCA) {
loop_hca(vgmstream->codec_data);
}
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 +2244,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 +2257,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;
@ -2265,16 +2287,21 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
}
snprintf(temp,TEMPSIZE,
"sample rate: %d Hz\n"
"sample rate: %d Hz\n",
vgmstream->sample_rate);
concatn(length,desc,temp);
snprintf(temp,TEMPSIZE,
"channels: %d\n",
vgmstream->sample_rate,
vgmstream->channels);
concatn(length,desc,temp);
if (vgmstream->loop_flag) {
if (vgmstream->loop_start_sample >= 0 && vgmstream->loop_end_sample > vgmstream->loop_start_sample) {
snprintf(temp,TEMPSIZE,
"looping: %s\n"
"loop start: %d samples (%.4f seconds)\n"
"loop end: %d samples (%.4f seconds)\n",
vgmstream->loop_flag ? "enabled" : "disabled",
vgmstream->loop_start_sample,
(double)vgmstream->loop_start_sample/vgmstream->sample_rate,
vgmstream->loop_end_sample,
@ -2465,15 +2492,15 @@ static void try_dual_file_stereo(VGMSTREAM * opened_vgmstream, STREAMFILE *strea
//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 */
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];
@ -2597,34 +2624,34 @@ fail:
/* 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;
}
@ -2662,7 +2689,7 @@ int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream) {
//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) {
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;
@ -2673,7 +2700,7 @@ int get_vgmstream_average_bitrate(VGMSTREAM * vgmstream) {
}
}
}
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;

View File

@ -5,8 +5,9 @@
#ifndef _VGMSTREAM_H
#define _VGMSTREAM_H
/* reasonable maxs */
enum { PATH_LIMIT = 32768 };
enum { STREAM_NAME_SIZE = 255 }; /* reasonable max */
enum { STREAM_NAME_SIZE = 255 };
#include "streamfile.h"
@ -717,15 +718,39 @@ typedef enum {
meta_GIN,
meta_DSF,
meta_208,
meta_DSP_DS2,
} meta_t;
#ifdef VGMSTREAM_MIXING
/* mixing info */
typedef enum {
MIX_SWAP,
MIX_ADD,
MIX_ADD_VOLUME,
MIX_VOLUME,
MIX_CROSSFADE,
MIX_DOWNMIX,
MIX_DOWNMIX_REST,
MIX_UPMIX
} mix_command_t;
typedef struct {
mix_command_t command;
int ch_a;
int ch_b;
float vol_a;
float vol_b;
float pos_a;
float pos_b;
} mix_config_data;
#endif
/* 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 */
@ -733,7 +758,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 */
@ -771,7 +796,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 */
@ -779,26 +804,33 @@ 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 */
/* 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]" */
#ifdef VGMSTREAM_MIXING
int output_channels; /* resulting channels after mixing (may be ignored if plugin doesn't support it) */
int mixing_on; /* mixing allowed */
int mixing_count; /* mixing number */
mix_config_data mixing[64]; /* applies transformation to output samples (could be alloc'ed but to simplify...) */
size_t mixing_size; /* mixing max */
#endif
/* 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;
@ -809,11 +841,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) */
@ -823,7 +850,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 */
@ -835,17 +862,20 @@ 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) */
/* 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) */
/* 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.
* 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;
@ -1092,14 +1122,14 @@ typedef struct {
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;
} 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;
@ -1282,7 +1312,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);
@ -1293,9 +1323,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) */
@ -1314,11 +1347,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);