mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-28 16:30:54 +01:00
commit
ae0f1b6175
544
cli/txtp_maker.py
Normal file
544
cli/txtp_maker.py
Normal 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()
|
@ -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;
|
||||
}
|
||||
|
||||
|
143
doc/TXTP.md
143
doc/TXTP.md
@ -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
|
||||
```
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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"},
|
||||
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) */
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
201
src/meta/txtp.c
201
src/meta/txtp.c
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
359
src/vgmstream.c
359
src/vgmstream.c
@ -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;
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user