mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-14 18:47:39 +01:00
Merge pull request #118 from bnnm/subsongs-pcmfloat-bik-snu
Subsongs, PCMFLOAT, BIK, SNU
This commit is contained in:
commit
95057a1561
@ -260,6 +260,7 @@ VGMSTREAM_DECLARE_FILE_TYPE("SND", snd);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SNDS", snds);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SNG", sng);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SNS", sns);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SNU", snu);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SPD", spd);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SPM", spm);
|
||||
VGMSTREAM_DECLARE_FILE_TYPE("SPS", sps);
|
||||
|
@ -24,7 +24,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
|
||||
// Dialog
|
||||
//
|
||||
|
||||
IDD_CONFIG DIALOGEX 0, 0, 187, 144
|
||||
IDD_CONFIG DIALOGEX 0, 0, 187, 156
|
||||
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
|
||||
FONT 8, "MS Shell Dlg", 400, 0, 0x0
|
||||
BEGIN
|
||||
@ -39,6 +39,7 @@ BEGIN
|
||||
CONTROL "Loop normally",IDC_LOOP_NORMALLY,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,57,77,10
|
||||
CONTROL "Loop forever",IDC_LOOP_FOREVER,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,70,77,10
|
||||
CONTROL "Ignore looping",IDC_IGNORE_LOOP,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,83,77,10
|
||||
CONTROL "Disable subsongs",IDC_DISABLE_SUBSONGS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,99,87,10
|
||||
END
|
||||
|
||||
|
||||
|
@ -23,12 +23,14 @@ static const GUID guid_cfg_IgnoreLoop = { 0xddda7ab6, 0x7bb6, 0x4abe, { 0xb9, 0x
|
||||
static const GUID guid_cfg_LoopCount = { 0xfc8dfd72, 0xfae8, 0x44cc, { 0xbe, 0x99, 0x1c, 0x7b, 0x27, 0x7a, 0xb6, 0xb9 } };
|
||||
static const GUID guid_cfg_FadeLength = { 0x61da7ef1, 0x56a5, 0x4368, { 0xae, 0x6, 0xec, 0x6f, 0xd7, 0xe6, 0x15, 0x5d } };
|
||||
static const GUID guid_cfg_FadeDelay = { 0x73907787, 0xaf49, 0x4659, { 0x96, 0x8e, 0x9f, 0x70, 0xa1, 0x62, 0x49, 0xc4 } };
|
||||
static const GUID guid_cfg_DisableSubsongs = { 0xa8cdd664, 0xb32b, 0x4a36, { 0x83, 0x07, 0xa0, 0x4c, 0xcd, 0x52, 0xa3, 0x7c } };
|
||||
|
||||
static cfg_bool cfg_LoopForever(guid_cfg_LoopForever, DEFAULT_LOOP_FOREVER);
|
||||
static cfg_bool cfg_IgnoreLoop(guid_cfg_IgnoreLoop, DEFAULT_IGNORE_LOOP);
|
||||
static cfg_string cfg_LoopCount(guid_cfg_LoopCount, DEFAULT_LOOP_COUNT);
|
||||
static cfg_string cfg_FadeLength(guid_cfg_FadeLength, DEFAULT_FADE_SECONDS);
|
||||
static cfg_string cfg_FadeDelay(guid_cfg_FadeDelay, DEFAULT_FADE_DELAY_SECONDS);
|
||||
static cfg_bool cfg_DisableSubsongs(guid_cfg_DisableSubsongs, DEFAULT_DISABLE_SUBSONGS);
|
||||
|
||||
// Needs to be here in rder to access the static config
|
||||
void input_vgmstream::load_settings()
|
||||
@ -39,6 +41,7 @@ void input_vgmstream::load_settings()
|
||||
sscanf(cfg_FadeDelay.get_ptr(),"%lf",&fade_delay_seconds);
|
||||
loop_forever = cfg_LoopForever;
|
||||
ignore_loop = cfg_IgnoreLoop;
|
||||
disable_subsongs = cfg_DisableSubsongs;
|
||||
}
|
||||
|
||||
const char * vgmstream_prefs::get_name()
|
||||
@ -70,6 +73,8 @@ BOOL vgmstreamPreferences::OnInitDialog(CWindow, LPARAM)
|
||||
uSetDlgItemText(m_hWnd, IDC_FADE_SECONDS, cfg_FadeLength);
|
||||
uSetDlgItemText(m_hWnd, IDC_FADE_DELAY_SECONDS, cfg_FadeDelay);
|
||||
|
||||
CheckDlgButton(IDC_DISABLE_SUBSONGS, cfg_DisableSubsongs?BST_CHECKED:BST_UNCHECKED);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -91,6 +96,8 @@ void vgmstreamPreferences::reset()
|
||||
uSetDlgItemText(m_hWnd, IDC_LOOP_COUNT, DEFAULT_LOOP_COUNT);
|
||||
uSetDlgItemText(m_hWnd, IDC_FADE_SECONDS, DEFAULT_FADE_SECONDS);
|
||||
uSetDlgItemText(m_hWnd, IDC_FADE_DELAY_SECONDS, DEFAULT_FADE_DELAY_SECONDS);
|
||||
|
||||
CheckDlgButton(IDC_DISABLE_SUBSONGS, DEFAULT_DISABLE_SUBSONGS?BST_CHECKED:BST_UNCHECKED);
|
||||
}
|
||||
|
||||
|
||||
@ -99,6 +106,7 @@ void vgmstreamPreferences::apply()
|
||||
{
|
||||
cfg_LoopForever = IsDlgButtonChecked(IDC_LOOP_FOREVER)?true:false;
|
||||
cfg_IgnoreLoop = IsDlgButtonChecked(IDC_IGNORE_LOOP)?true:false;
|
||||
cfg_DisableSubsongs = IsDlgButtonChecked(IDC_DISABLE_SUBSONGS)?true:false;
|
||||
|
||||
double temp_fade_seconds;
|
||||
double temp_fade_delay_seconds;
|
||||
@ -152,6 +160,9 @@ bool vgmstreamPreferences::HasChanged()
|
||||
if(IsDlgButtonChecked(IDC_LOOP_NORMALLY))
|
||||
if(cfg_IgnoreLoop != false || cfg_LoopForever != false) return true;
|
||||
|
||||
bool current_cfg_DisableSubsongs = IsDlgButtonChecked(IDC_DISABLE_SUBSONGS)?true:false;
|
||||
if(cfg_DisableSubsongs != current_cfg_DisableSubsongs) return true;
|
||||
|
||||
pfc::string FadeLength(cfg_FadeLength);
|
||||
pfc::string FadeDelay(cfg_FadeDelay);
|
||||
pfc::string LoopCount(cfg_LoopCount);
|
||||
|
@ -14,6 +14,7 @@
|
||||
#define DEFAULT_LOOP_COUNT "2.00"
|
||||
#define DEFAULT_LOOP_FOREVER false
|
||||
#define DEFAULT_IGNORE_LOOP false
|
||||
#define DEFAULT_DISABLE_SUBSONGS false
|
||||
|
||||
class vgmstreamPreferences : public CDialogImpl<vgmstreamPreferences>, public preferences_page_instance {
|
||||
public:
|
||||
@ -40,6 +41,7 @@ public:
|
||||
COMMAND_HANDLER_EX(IDC_FADE_SECONDS, EN_CHANGE, OnEditChange)
|
||||
COMMAND_HANDLER_EX(IDC_FADE_DELAY_SECONDS, EN_CHANGE, OnEditChange)
|
||||
COMMAND_HANDLER_EX(IDC_LOOP_COUNT, EN_CHANGE, OnEditChange)
|
||||
COMMAND_HANDLER_EX(IDC_DISABLE_SUBSONGS, BN_CLICKED, OnEditChange)
|
||||
END_MSG_MAP()
|
||||
private:
|
||||
BOOL OnInitDialog(CWindow, LPARAM);
|
||||
|
@ -58,7 +58,7 @@ input_vgmstream::input_vgmstream() {
|
||||
loop_count = 2.0f;
|
||||
loop_forever = false;
|
||||
ignore_loop = 0;
|
||||
disable_subsongs = true;
|
||||
disable_subsongs = false;
|
||||
|
||||
load_settings();
|
||||
}
|
||||
@ -134,7 +134,11 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal
|
||||
pfc::string8 description;
|
||||
pfc::string8_fast temp;
|
||||
|
||||
get_subsong_info(p_subsong, NULL, &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_start, &loop_end, &samplerate, &channels, &bitrate, description, p_abort);
|
||||
|
||||
if (get_subsong_count() > 1) {
|
||||
p_info.meta_set("TITLE",temp);
|
||||
}
|
||||
|
||||
p_info.info_set_int("samplerate", samplerate);
|
||||
p_info.info_set_int("channels", channels);
|
||||
@ -156,7 +160,7 @@ void input_vgmstream::get_info(t_uint32 p_subsong, file_info & p_info, abort_cal
|
||||
|
||||
if (get_description_tag(temp,description,"block size: ")) p_info.info_set("block_size",temp);
|
||||
if (get_description_tag(temp,description,"metadata from: ")) p_info.info_set("metadata_source",temp);
|
||||
if (get_description_tag(temp,description,"stream number: ")) p_info.info_set("stream_number",temp);
|
||||
if (get_description_tag(temp,description,"stream count: ")) p_info.info_set("stream_count",temp);
|
||||
if (get_description_tag(temp,description,"stream index: ")) p_info.info_set("stream_index",temp);
|
||||
if (get_description_tag(temp,description,"stream name: ")) p_info.info_set("stream_name",temp);
|
||||
}
|
||||
@ -343,8 +347,9 @@ void input_vgmstream::setup_vgmstream(abort_callback & p_abort) {
|
||||
fade_samples = (int)(fade_seconds * vgmstream->sample_rate);
|
||||
}
|
||||
|
||||
void input_vgmstream::get_subsong_info(t_uint32 p_subsong, char *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_start, int *loop_end, int *sample_rate, int *channels, int *bitrate, pfc::string_base & description, abort_callback & p_abort) {
|
||||
VGMSTREAM * infostream = NULL;
|
||||
char temp[1024];
|
||||
|
||||
// reuse current vgmstream if not querying a new subsong
|
||||
if (subsong != p_subsong) {
|
||||
@ -376,9 +381,23 @@ void input_vgmstream::get_subsong_info(t_uint32 p_subsong, char *title, int *len
|
||||
}
|
||||
|
||||
if (title) {
|
||||
const char *p=filename+strlen(filename);
|
||||
const char *p = filename + strlen(filename);
|
||||
while (*p != '\\' && p >= filename) p--;
|
||||
strcpy(title,++p);
|
||||
p++;
|
||||
const char *e = filename + strlen(filename);
|
||||
while (*e != '.' && e >= filename) e--;
|
||||
|
||||
title.set_string(p, e - p);
|
||||
|
||||
if (!disable_subsongs && infostream && infostream->num_streams > 1) {
|
||||
sprintf(temp,"#%d",p_subsong);
|
||||
title += temp;
|
||||
|
||||
if (infostream->stream_name[0] != '\0') {
|
||||
sprintf(temp," (%s)",infostream->stream_name);
|
||||
title += temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// and only close if was querying a new subsong
|
||||
|
@ -60,7 +60,7 @@ class input_vgmstream {
|
||||
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, char *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_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 & description, const char *tag, char delimiter = '\n');
|
||||
};
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#define IDC_THREAD_PRIORITY_SLIDER 1006
|
||||
#define IDC_THREAD_PRIORITY_TEXT 1007
|
||||
#define IDC_DEFAULT_BUTTON 1008
|
||||
#define IDC_DISABLE_SUBSONGS 1009
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
|
@ -63,6 +63,7 @@ void decode_pcm8_sb_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channels
|
||||
void decode_pcm8_unsigned_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_pcm8_unsigned(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_ulaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_pcmfloat(VGMSTREAM *vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
size_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample);
|
||||
|
||||
/* psx_decoder */
|
||||
@ -77,12 +78,15 @@ size_t ps_bytes_to_samples(size_t bytes, int channels);
|
||||
void decode_xa(VGMSTREAM * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void init_get_high_nibble(VGMSTREAM * vgmstream);
|
||||
|
||||
/* ea_decoder */
|
||||
/* ea_xa_decoder */
|
||||
void decode_ea_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void decode_ea_xa_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
void decode_ea_xa_v2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do,int channel);
|
||||
void decode_maxis_xa(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
|
||||
/* ea_xas_decoder */
|
||||
void decode_ea_xas(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel);
|
||||
|
||||
/* sdx2_decoder */
|
||||
void decode_sdx2(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
void decode_sdx2_int(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do);
|
||||
@ -197,7 +201,9 @@ void free_at3plus(maiatrac3plus_codec_data *data);
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
/* ffmpeg_decoder */
|
||||
ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size);
|
||||
ffmpeg_codec_data * init_ffmpeg_offset_index(STREAMFILE *streamFile, uint64_t start, uint64_t size, int stream_index);
|
||||
ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size);
|
||||
ffmpeg_codec_data * init_ffmpeg_header_offset_index(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size, int stream_index);
|
||||
|
||||
void decode_ffmpeg(VGMSTREAM *stream, sample * outbuf, int32_t samples_to_do, int channels);
|
||||
void reset_ffmpeg(VGMSTREAM *vgmstream);
|
||||
|
78
src/coding/ea_xas_decoder.c
Normal file
78
src/coding/ea_xas_decoder.c
Normal file
@ -0,0 +1,78 @@
|
||||
#include "coding.h"
|
||||
#include "../util.h"
|
||||
|
||||
static const int EA_XA_TABLE[20] = {
|
||||
0, 240, 460, 392,
|
||||
0, 0, -208, -220,
|
||||
0, 1, 3, 4,
|
||||
7, 8, 10, 11,
|
||||
0, -1, -3, -4
|
||||
};
|
||||
|
||||
/* EA-XAS, evolution of EA-XA and cousin of MTA2. Layout: blocks of 0x4c per channel (128 samples),
|
||||
* divided into 4 headers + 4 vertical groups of 15 bytes (for parallelism?).
|
||||
* To simplify, always decodes the block and discards unneeded samples, so doesn't use external hist. */
|
||||
void decode_ea_xas(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do, int channel) {
|
||||
int group, row, i;
|
||||
int samples_done = 0, sample_count = 0;
|
||||
|
||||
|
||||
/* internal interleave */
|
||||
int block_samples = 128;
|
||||
first_sample = first_sample % block_samples;
|
||||
|
||||
|
||||
/* process groups */
|
||||
for (group = 0; group < 4; group++) {
|
||||
int coef1, coef2;
|
||||
int16_t hist1, hist2;
|
||||
uint8_t shift;
|
||||
uint32_t group_header = (uint32_t)read_32bitLE(stream->offset + channel*0x4c + group*0x4, stream->streamfile); /* always LE */
|
||||
|
||||
coef1 = EA_XA_TABLE[(uint8_t)(group_header & 0x0F) + 0];
|
||||
coef2 = EA_XA_TABLE[(uint8_t)(group_header & 0x0F) + 4];
|
||||
hist2 = (int16_t)(group_header & 0xFFF0);
|
||||
hist1 = (int16_t)((group_header >> 16) & 0xFFF0);
|
||||
shift = 20 - ((group_header >> 16) & 0x0F);
|
||||
|
||||
/* write header samples (needed) */
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist2;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = hist1;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
/* process nibbles per group */
|
||||
for (row = 0; row < 15; row++) {
|
||||
for (i = 0; i < 1*2; i++) {
|
||||
uint8_t sample_byte = (uint8_t)read_8bit(stream->offset + channel*0x4c + 4*4 + row*0x04 + group + i/2, stream->streamfile);
|
||||
int sample;
|
||||
|
||||
sample = get_nibble_signed(sample_byte, !(i&1)); /* upper first */
|
||||
sample = sample << shift;
|
||||
sample = (sample + hist1 * coef1 + hist2 * coef2 + 128) >> 8;
|
||||
sample = clamp16(sample);
|
||||
|
||||
if (sample_count >= first_sample && samples_done < samples_to_do) {
|
||||
outbuf[samples_done * channelspacing] = sample;
|
||||
samples_done++;
|
||||
}
|
||||
sample_count++;
|
||||
|
||||
hist2 = hist1;
|
||||
hist1 = sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* internal interleave (interleaved channels, but manually advances to co-exist with ea blocks) */
|
||||
if (first_sample + samples_done == block_samples) {
|
||||
stream->offset += 0x4c * channelspacing;
|
||||
}
|
||||
}
|
@ -272,13 +272,16 @@ static int64_t ffmpeg_seek(void *opaque, int64_t offset, int whence) {
|
||||
/* MAIN INIT/DECODER */
|
||||
/* ******************************************** */
|
||||
|
||||
/**
|
||||
* Manually init FFmpeg, from an offset.
|
||||
* Used if the stream has internal data recognized by FFmpeg.
|
||||
*/
|
||||
ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, uint64_t size) {
|
||||
return init_ffmpeg_header_offset(streamFile, NULL, 0, start, size);
|
||||
return init_ffmpeg_header_offset_index(streamFile, NULL,0, start,size, 0);
|
||||
}
|
||||
ffmpeg_codec_data * init_ffmpeg_offset_index(STREAMFILE *streamFile, uint64_t start, uint64_t size, int stream_index) {
|
||||
return init_ffmpeg_header_offset_index(streamFile, NULL,0, start,size, stream_index);
|
||||
}
|
||||
ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size) {
|
||||
return init_ffmpeg_header_offset_index(streamFile, header,header_size, start,size, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Manually init FFmpeg, from a fake header / offset.
|
||||
@ -286,15 +289,17 @@ ffmpeg_codec_data * init_ffmpeg_offset(STREAMFILE *streamFile, uint64_t start, u
|
||||
* Takes a fake header, to trick FFmpeg into demuxing/decoding the stream.
|
||||
* This header will be seamlessly inserted before 'start' offset, and total filesize will be 'header_size' + 'size'.
|
||||
* The header buffer will be copied and memory-managed internally.
|
||||
* NULL header can used given if the stream has internal data recognized by FFmpeg at offset.
|
||||
* Stream index can be passed to FFmpeg, if the format has multiple streams (1=first).
|
||||
*/
|
||||
ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size) {
|
||||
ffmpeg_codec_data * init_ffmpeg_header_offset_index(STREAMFILE *streamFile, uint8_t * header, uint64_t header_size, uint64_t start, uint64_t size, int stream_index) {
|
||||
char filename[PATH_LIMIT];
|
||||
ffmpeg_codec_data * data;
|
||||
int errcode, i;
|
||||
|
||||
int streamIndex, streamCount;
|
||||
AVStream *stream;
|
||||
AVCodecParameters *codecPar;
|
||||
AVCodecParameters *codecPar = NULL;
|
||||
|
||||
AVRational tb;
|
||||
|
||||
@ -338,23 +343,28 @@ ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t *
|
||||
if ((errcode = avformat_find_stream_info(data->formatCtx, NULL)) < 0) goto fail;
|
||||
|
||||
|
||||
/* find valid audio stream inside */
|
||||
/* find valid audio stream */
|
||||
streamIndex = -1;
|
||||
streamCount = 0; /* audio streams only */
|
||||
streamCount = 0;
|
||||
|
||||
for (i = 0; i < data->formatCtx->nb_streams; ++i) {
|
||||
stream = data->formatCtx->streams[i];
|
||||
codecPar = stream->codecpar;
|
||||
if (streamIndex < 0 && codecPar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
streamIndex = i; /* select first audio stream found */
|
||||
} else {
|
||||
stream->discard = AVDISCARD_ALL; /* disable demuxing unneded streams */
|
||||
}
|
||||
if (codecPar->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||
streamCount++;
|
||||
}
|
||||
|
||||
if (streamIndex < 0) goto fail;
|
||||
if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
streamCount++;
|
||||
|
||||
/* select Nth audio stream if specified, or first one */
|
||||
if (streamIndex < 0 || (stream_index > 0 && streamCount == stream_index)) {
|
||||
codecPar = stream->codecpar;
|
||||
streamIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (i != streamIndex)
|
||||
stream->discard = AVDISCARD_ALL; /* disable demuxing for other streams */
|
||||
}
|
||||
if (streamCount < stream_index) goto fail;
|
||||
if (streamIndex < 0 || !codecPar) goto fail;
|
||||
|
||||
data->streamIndex = streamIndex;
|
||||
stream = data->formatCtx->streams[streamIndex];
|
||||
@ -386,6 +396,7 @@ ffmpeg_codec_data * init_ffmpeg_header_offset(STREAMFILE *streamFile, uint8_t *
|
||||
data->bytesConsumedFromDecodedFrame = INT_MAX;
|
||||
|
||||
|
||||
|
||||
/* other setup */
|
||||
data->sampleRate = data->codecCtx->sample_rate;
|
||||
data->channels = data->codecCtx->channels;
|
||||
|
@ -118,6 +118,22 @@ void decode_ulaw(VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing,
|
||||
}
|
||||
}
|
||||
|
||||
void decode_pcmfloat(VGMSTREAM *vgmstream, VGMSTREAMCHANNEL * stream, sample * outbuf, int channelspacing, int32_t first_sample, int32_t samples_to_do) {
|
||||
int i, sample_count;
|
||||
int32_t (*read_32bit)(off_t,STREAMFILE*) = vgmstream->codec_endian ? read_32bitBE : read_32bitLE;
|
||||
|
||||
for (i=first_sample,sample_count=0; i<first_sample+samples_to_do; i++,sample_count+=channelspacing) {
|
||||
uint32_t sample_int = read_32bit(stream->offset+i*4,stream->streamfile);
|
||||
float sample_float;
|
||||
int sample_pcm;
|
||||
|
||||
memcpy(&sample_float, &sample_int, 4); /* maybe unorthodox but simplest */
|
||||
sample_pcm = floor(sample_float * 32767.f + .5f);
|
||||
|
||||
outbuf[sample_count] = clamp16(sample_pcm);
|
||||
}
|
||||
}
|
||||
|
||||
size_t pcm_bytes_to_samples(size_t bytes, int channels, int bits_per_sample) {
|
||||
return bytes / channels / (bits_per_sample/8);
|
||||
}
|
||||
|
@ -254,6 +254,7 @@ static const char* extension_list[] = {
|
||||
"snds",
|
||||
"sng",
|
||||
"sns",
|
||||
"snu",
|
||||
"spd",
|
||||
"spm",
|
||||
"sps",
|
||||
@ -404,6 +405,7 @@ static const coding_info coding_info_list[] = {
|
||||
{coding_PCM8_int, "8-bit PCM with 1 byte interleave"},
|
||||
{coding_PCM8_SB_int, "8-bit PCM with sign bit, 1 byte interleave"},
|
||||
{coding_ULAW, "8-bit u-Law"},
|
||||
{coding_PCMFLOAT, "32-bit float PCM"},
|
||||
{coding_CRI_ADX, "CRI ADX 4-bit ADPCM"},
|
||||
{coding_CRI_ADX_exp, "CRI ADX 4-bit ADPCM with exponential scale"},
|
||||
{coding_CRI_ADX_fixed, "CRI ADX 4-bit ADPCM with fixed coefficients"},
|
||||
@ -463,6 +465,7 @@ static const coding_info coding_info_list[] = {
|
||||
{coding_MTAF, "Konami MTAF 4-bit ADPCM"},
|
||||
{coding_MTA2, "Konami MTA2 4-bit ADPCM"},
|
||||
{coding_MC3, "Paradigm MC3 3-bit ADPCM"},
|
||||
{coding_EA_XAS, "Electronic Arts EA-XAS 4-bit ADPCM"},
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
{coding_ogg_vorbis, "Ogg Vorbis"},
|
||||
@ -499,8 +502,8 @@ static const layout_info layout_info_list[] = {
|
||||
{layout_ast_blocked, "AST blocked"},
|
||||
{layout_halpst_blocked, "HALPST blocked"},
|
||||
{layout_xa_blocked, "CD-ROM XA"},
|
||||
{layout_ea_blocked, "Electronic Arts Audio Blocks"},
|
||||
{layout_eacs_blocked, "Electronic Arts (Old Version) Audio Blocks"},
|
||||
{layout_ea_blocked, "Electronic Arts SCxx blocked"},
|
||||
{layout_eacs_blocked, "Electronic Arts EACS blocked"},
|
||||
{layout_caf_blocked, "CAF blocked"},
|
||||
{layout_wsi_blocked, ".wsi blocked"},
|
||||
{layout_xvas_blocked, ".xvas blocked"},
|
||||
@ -528,6 +531,7 @@ static const layout_info layout_info_list[] = {
|
||||
{layout_aix, "AIX interleave, internally 18-byte interleaved"},
|
||||
{layout_aax, "AAX blocked, 18-byte interleaved"},
|
||||
{layout_scd_int, "SCD multistream interleave"},
|
||||
{layout_ea_sns_blocked, "Electronic Arts SNS blocked"},
|
||||
#ifdef VGM_USE_VORBIS
|
||||
{layout_ogg_vorbis, "Ogg"},
|
||||
#endif
|
||||
@ -874,6 +878,8 @@ static const meta_info meta_info_list[] = {
|
||||
{meta_SK_AUD, "Silicon Knights AUD header"},
|
||||
{meta_AHX, "CRI AHX header"},
|
||||
{meta_STM, "Angel Studios/Rockstar San Diego STMA header"},
|
||||
{meta_BINK, "RAD Game Tools Bink header"},
|
||||
{meta_EA_SNU, "Electronic Arts SNU header"},
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
{meta_OGG_VORBIS, "Ogg Vorbis"},
|
||||
|
@ -51,7 +51,8 @@ void render_vgmstream_blocked(sample * buffer, int32_t sample_count, VGMSTREAM *
|
||||
vgmstream->current_sample += samples_to_do;
|
||||
vgmstream->samples_into_block+=samples_to_do;
|
||||
|
||||
if (vgmstream->samples_into_block==samples_this_block) {
|
||||
if (vgmstream->samples_into_block==samples_this_block
|
||||
/*&& vgmstream->current_sample < vgmstream->num_samples*/) { /* don't go past last block */
|
||||
switch (vgmstream->layout_type) {
|
||||
case layout_ast_blocked:
|
||||
ast_block_update(vgmstream->next_block_offset,vgmstream);
|
||||
@ -139,6 +140,9 @@ void render_vgmstream_blocked(sample * buffer, int32_t sample_count, VGMSTREAM *
|
||||
break;
|
||||
case layout_hwas_blocked:
|
||||
hwas_block_update(vgmstream->next_block_offset,vgmstream);
|
||||
break;
|
||||
case layout_ea_sns_blocked:
|
||||
ea_sns_block_update(vgmstream->next_block_offset,vgmstream);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
38
src/layout/ea_sns_blocked.c
Normal file
38
src/layout/ea_sns_blocked.c
Normal file
@ -0,0 +1,38 @@
|
||||
#include "layout.h"
|
||||
#include "../coding/coding.h"
|
||||
#include "../vgmstream.h"
|
||||
|
||||
/* EA "SNS "blocks (most common in .SNS) */
|
||||
void ea_sns_block_update(off_t block_offset, VGMSTREAM * vgmstream) {
|
||||
STREAMFILE* streamFile = vgmstream->ch[0].streamfile;
|
||||
uint32_t block_size, block_samples;
|
||||
size_t file_size = get_streamfile_size(streamFile);
|
||||
int i;
|
||||
|
||||
/* always BE */
|
||||
block_size = read_32bitBE(block_offset + 0x00,streamFile);
|
||||
block_samples = read_32bitBE(block_offset + 0x04,streamFile);
|
||||
|
||||
/* EOF */
|
||||
if (block_size == 0 || block_offset >= file_size) {
|
||||
vgmstream->current_block_offset = file_size;
|
||||
vgmstream->next_block_offset = file_size + 0x04;
|
||||
vgmstream->current_block_samples = vgmstream->num_samples;
|
||||
return;
|
||||
}
|
||||
|
||||
/* known: 0x80 = last block, 0x40, 0x08, 0x04, 0x01 */
|
||||
if (block_size & 0xFF000000) {
|
||||
VGM_ASSERT(!(block_size & 0x80000000), "EA SNS: unknown flag found at %lx\n", block_offset);
|
||||
block_size &= 0x00FFFFFF;
|
||||
}
|
||||
|
||||
for (i = 0; i < vgmstream->channels; i++) {
|
||||
off_t channel_start = 0x00;
|
||||
vgmstream->ch[i].offset = block_offset + 0x08 + channel_start;
|
||||
}
|
||||
|
||||
vgmstream->current_block_offset = block_offset;
|
||||
vgmstream->next_block_offset = block_offset + block_size;
|
||||
vgmstream->current_block_samples = block_samples;
|
||||
}
|
@ -63,6 +63,8 @@ void rws_block_update(off_t block_offset, VGMSTREAM * vgmstream);
|
||||
|
||||
void hwas_block_update(off_t block_offset, VGMSTREAM * vgmstream);
|
||||
|
||||
void ea_sns_block_update(off_t block_offset, VGMSTREAM * vgmstream);
|
||||
|
||||
/* other layouts */
|
||||
void render_vgmstream_interleave(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstream);
|
||||
|
||||
|
@ -348,6 +348,10 @@
|
||||
RelativePath=".\meta\ea_old.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\ea_snu.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\meta\emff.c"
|
||||
>
|
||||
@ -1343,9 +1347,13 @@
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\coding\ea_decoder.c"
|
||||
RelativePath=".\coding\ea_xa_decoder.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\coding\ea_xas_decoder.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\coding\ffmpeg_decoder.c"
|
||||
>
|
||||
@ -1534,6 +1542,10 @@
|
||||
RelativePath=".\layout\ea_block.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\layout\ea_sns_blocked.c"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\layout\emff_blocked.c"
|
||||
>
|
||||
|
@ -210,6 +210,7 @@
|
||||
<ClCompile Include="meta\ea_schl.c" />
|
||||
<ClCompile Include="meta\ea_schl_fixed.c" />
|
||||
<ClCompile Include="meta\ea_old.c" />
|
||||
<ClCompile Include="meta\ea_snu.c" />
|
||||
<ClCompile Include="meta\emff.c" />
|
||||
<ClCompile Include="meta\exakt_sc.c" />
|
||||
<ClCompile Include="meta\ffw.c" />
|
||||
@ -415,7 +416,8 @@
|
||||
<ClCompile Include="coding\acm_decoder.c" />
|
||||
<ClCompile Include="coding\adx_decoder.c" />
|
||||
<ClCompile Include="coding\aica_decoder.c" />
|
||||
<ClCompile Include="coding\ea_decoder.c" />
|
||||
<ClCompile Include="coding\ea_xa_decoder.c" />
|
||||
<ClCompile Include="coding\ea_xas_decoder.c" />
|
||||
<ClCompile Include="coding\g719_decoder.c" />
|
||||
<ClCompile Include="coding\g721_decoder.c" />
|
||||
<ClCompile Include="coding\g7221_decoder.c" />
|
||||
@ -454,6 +456,7 @@
|
||||
<ClCompile Include="layout\caf_blocked.c" />
|
||||
<ClCompile Include="layout\de2_blocked.c" />
|
||||
<ClCompile Include="layout\ea_block.c" />
|
||||
<ClCompile Include="layout\ea_sns_blocked.c" />
|
||||
<ClCompile Include="layout\emff_blocked.c" />
|
||||
<ClCompile Include="layout\filp_blocked.c" />
|
||||
<ClCompile Include="layout\gsb_blocked.c" />
|
||||
|
@ -193,6 +193,9 @@
|
||||
<ClCompile Include="meta\ea_old.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\ea_snu.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="meta\emff.c">
|
||||
<Filter>meta\Source Files</Filter>
|
||||
</ClCompile>
|
||||
@ -784,7 +787,10 @@
|
||||
<ClCompile Include="coding\aica_decoder.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="coding\ea_decoder.c">
|
||||
<ClCompile Include="coding\ea_xa_decoder.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="coding\ea_xas_decoder.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="coding\g721_decoder.c">
|
||||
@ -898,6 +904,9 @@
|
||||
<ClCompile Include="layout\ea_block.c">
|
||||
<Filter>layout\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="layout\ea_sns_blocked.c">
|
||||
<Filter>layout\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="layout\emff_blocked.c">
|
||||
<Filter>layout\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
150
src/meta/bik.c
150
src/meta/bik.c
@ -2,81 +2,94 @@
|
||||
#include "../coding/coding.h"
|
||||
#include "../util.h"
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
static int bink_get_info(STREAMFILE *streamFile, int * out_total_streams, int * out_channel_count, int * out_sample_rate, int * out_num_samples);
|
||||
|
||||
static uint32_t bik_get_num_samples(STREAMFILE *streamFile, int bits_per_sample);
|
||||
|
||||
/* BIK 1/2 - RAD Game Tools movies (audio/video format) */
|
||||
/* BINK 1/2 - RAD Game Tools movies (audio/video format) */
|
||||
VGMSTREAM * init_vgmstream_bik(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
ffmpeg_codec_data *data = NULL;
|
||||
int channel_count = 0, loop_flag = 0, sample_rate = 0, num_samples = 0, total_streams = 0;
|
||||
int stream_index = streamFile->stream_index;
|
||||
|
||||
|
||||
/* check extension, case insensitive (bika = manually demuxed audio) */
|
||||
if (!check_extensions(streamFile,"bik,bika,bik2,bik2a,bk2,bk2a")) goto fail;
|
||||
|
||||
/* check header "BIK" (bik1) or "KB2" (bik2) (followed by version-char) */
|
||||
/* check header "BIK" (bink 1) or "KB2" (bink 2), followed by version-char (audio is the same for both) */
|
||||
if ((read_32bitBE(0x00,streamFile) & 0xffffff00) != 0x42494B00 &&
|
||||
(read_32bitBE(0x00,streamFile) & 0xffffff00) != 0x4B423200 ) goto fail;
|
||||
|
||||
/* FFmpeg can parse BIK audio, but can't get the number of samples, which vgmstream needs.
|
||||
* The only way to get them is to read all frame headers */
|
||||
data = init_ffmpeg_offset(streamFile, 0x0, get_streamfile_size(streamFile));
|
||||
if (!data) goto fail;
|
||||
|
||||
vgmstream = allocate_vgmstream(data->channels, 0); /* alloc FFmpeg first to get channel count */
|
||||
if (!vgmstream) goto fail;
|
||||
vgmstream->codec_data = data;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->meta_type = meta_FFmpeg;
|
||||
vgmstream->sample_rate = data->sampleRate;
|
||||
|
||||
/* manually get num_samples since data->totalSamples is always 0 */
|
||||
vgmstream->num_samples = bik_get_num_samples(streamFile, data->bitsPerSample);
|
||||
if (vgmstream->num_samples == 0)
|
||||
/* find target stream info and samples */
|
||||
if (!bink_get_info(streamFile, &total_streams, &channel_count, &sample_rate, &num_samples))
|
||||
goto fail;
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count, loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->layout_type = layout_none;
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->num_streams = total_streams;
|
||||
vgmstream->meta_type = meta_BINK;
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
/* init_FFmpeg uses streamFile->stream_index internally, if specified */
|
||||
vgmstream->codec_data = init_ffmpeg_offset_index(streamFile, 0x0, get_streamfile_size(streamFile), stream_index);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
free_ffmpeg(data);
|
||||
if (vgmstream) {
|
||||
vgmstream->codec_data = NULL;
|
||||
close_vgmstream(vgmstream);
|
||||
}
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of samples in a BIK file by reading all frames' headers,
|
||||
* as they are not in the main header. The header for BIK1 and 2 is the same.
|
||||
* Gets stream info, and number of samples in a BINK file by reading all frames' headers (as it's VBR),
|
||||
* as they are not in the main header. The header for BINK1 and 2 is the same.
|
||||
* (a ~3 min movie needs ~6000-7000 frames = fseeks, should be fast enough)
|
||||
*
|
||||
* Needs bits per sample to calculate PCM samples, since most bink audio seems to use 32, actually.
|
||||
*/
|
||||
static uint32_t bik_get_num_samples(STREAMFILE *streamFile, int bits_per_sample) {
|
||||
static int bink_get_info(STREAMFILE *streamFile, int * out_total_streams, int * out_channel_count, int * out_sample_rate, int * out_num_samples) {
|
||||
uint32_t *offsets = NULL;
|
||||
uint32_t num_samples_b = 0;
|
||||
uint32_t num_frames, num_samples_b = 0;
|
||||
off_t cur_offset;
|
||||
size_t filesize;
|
||||
int i, j, num_frames, num_tracks;
|
||||
int target_stream = 0;
|
||||
int i, j, sample_rate, channel_count;
|
||||
int total_streams, target_stream = streamFile->stream_index;
|
||||
|
||||
filesize = get_streamfile_size(streamFile);
|
||||
size_t filesize = get_streamfile_size(streamFile);
|
||||
uint32_t signature = (read_32bitBE(0x00,streamFile) & 0xffffff00);
|
||||
uint8_t revision = (read_32bitBE(0x00,streamFile) & 0xFF);
|
||||
|
||||
num_frames = read_32bitLE(0x08,streamFile);
|
||||
if (num_frames <= 0) goto fail;
|
||||
if (num_frames > 0x100000) goto fail; /* something must be off (avoids big allocs below) */
|
||||
if (read_32bitLE(0x04,streamFile)+ 0x08 != filesize)
|
||||
goto fail;
|
||||
|
||||
/* multichannel audio is usually N tracks of stereo/mono, no way to know channel layout */
|
||||
num_tracks = read_32bitLE(0x28,streamFile);
|
||||
if (num_tracks<=0 || num_tracks > 255) goto fail;
|
||||
num_frames = (uint32_t)read_32bitLE(0x08,streamFile);
|
||||
if (num_frames == 0 || num_frames > 0x100000) goto fail; /* something must be off (avoids big allocs below) */
|
||||
|
||||
/* find the frame index table, which is after 3 audio headers of size 4 for each track */
|
||||
cur_offset = 0x2c + num_tracks*4 * 3;
|
||||
/* multichannel/multilanguage audio is usually N streams of stereo/mono, no way to know channel layout */
|
||||
total_streams = read_32bitLE(0x28,streamFile);
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
if (target_stream < 0 || target_stream > total_streams || total_streams < 1 || total_streams > 255) goto fail;
|
||||
|
||||
/* read offsets in a buffer, to avoid fseeking to the table back and forth
|
||||
* the number of frames can be highly variable so we'll alloc */
|
||||
/* find stream info and position in offset table */
|
||||
cur_offset = 0x2c;
|
||||
if ((signature == 0x42494B00 && (revision == 0x6b)) || /* k */
|
||||
(signature == 0x4B423200 && (revision == 0x69 || revision == 0x6a || revision == 0x6b))) /* i,j,k */
|
||||
cur_offset += 0x04; /* unknown v2 header field */
|
||||
cur_offset += 0x04*total_streams; /* skip streams max packet bytes */
|
||||
sample_rate = (uint16_t)read_16bitLE(cur_offset+0x04*(target_stream-1)+0x00,streamFile);
|
||||
channel_count = (uint16_t)read_16bitLE(cur_offset+0x04*(target_stream-1)+0x02,streamFile) & 0x2000 ? 2 : 1; /* stereo flag */
|
||||
cur_offset += 0x04*total_streams; /* skip streams info */
|
||||
cur_offset += 0x04*total_streams; /* skip streams ids */
|
||||
|
||||
|
||||
/* read frame offsets in a buffer, to avoid fseeking to the table back and forth */
|
||||
offsets = malloc(sizeof(uint32_t) * num_frames);
|
||||
if (!offsets) goto fail;
|
||||
|
||||
@ -87,41 +100,40 @@ static uint32_t bik_get_num_samples(STREAMFILE *streamFile, int bits_per_sample)
|
||||
if (offsets[i] > filesize) goto fail;
|
||||
}
|
||||
/* after the last index is the file size, validate just in case */
|
||||
if (read_32bitLE(cur_offset,streamFile)!=filesize) goto fail;
|
||||
|
||||
/* multistream support just for fun (FFmpeg should select the same target stream)
|
||||
* (num_samples for other streams seem erratic though) */
|
||||
if (target_stream == 0) target_stream = 1;
|
||||
if (target_stream < 0 || target_stream > num_tracks || num_tracks < 1) goto fail;
|
||||
if (read_32bitLE(cur_offset,streamFile) != filesize) goto fail;
|
||||
|
||||
/* read each frame header and sum all samples
|
||||
* a frame has N audio packets with header (one per track) + video packet */
|
||||
* a frame has N audio packets with a header (one per stream) + video packet */
|
||||
for (i=0; i < num_frames; i++) {
|
||||
cur_offset = offsets[i];
|
||||
|
||||
/* read audio packet headers */
|
||||
for (j=0; j < num_tracks; j++) {
|
||||
uint32_t ap_size, samples_b;
|
||||
ap_size = read_32bitLE(cur_offset+0x00,streamFile); /* not counting this int */
|
||||
samples_b = read_32bitLE(cur_offset+0x04,streamFile); /* decoded samples in bytes */
|
||||
if (ap_size==0) break; /* no audio in this frame */
|
||||
/* read audio packet headers per stream */
|
||||
for (j=0; j < total_streams; j++) {
|
||||
uint32_t ap_size = read_32bitLE(cur_offset+0x00,streamFile); /* not counting this int */
|
||||
|
||||
if (j == target_stream-1) { /* target samples found, read next frame */
|
||||
num_samples_b += samples_b;
|
||||
break;
|
||||
} else { /* check next audio packet */
|
||||
cur_offset += 4 + ap_size; /* todo sometimes ap_size doesn't include itself (+4), others it does? */
|
||||
if (j == target_stream-1) {
|
||||
if (ap_size > 0)
|
||||
num_samples_b += read_32bitLE(cur_offset+0x04,streamFile); /* decoded samples in bytes */
|
||||
break; /* next frame */
|
||||
}
|
||||
else { /* next stream packet or frame */
|
||||
cur_offset += 4 + ap_size; //todo sometimes ap_size doesn't include itself (+4), others it does?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
free(offsets);
|
||||
return num_samples_b / (bits_per_sample / 8);
|
||||
|
||||
|
||||
if (out_total_streams) *out_total_streams = total_streams;
|
||||
if (out_sample_rate) *out_sample_rate = sample_rate;
|
||||
if (out_channel_count) *out_channel_count = channel_count;
|
||||
//todo returns a few more samples (~48) than binkconv.exe?
|
||||
if (out_num_samples) *out_num_samples = num_samples_b / (2 * channel_count);
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
free(offsets);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
107
src/meta/ea_snu.c
Normal file
107
src/meta/ea_snu.c
Normal file
@ -0,0 +1,107 @@
|
||||
#include "meta.h"
|
||||
#include "../layout/layout.h"
|
||||
|
||||
/* .SNU - EA new-ish header (Dead Space, The Godfather 2) */
|
||||
VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE *streamFile) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
int channel_count, loop_flag = 0, channel_config, codec, sample_rate, flags;
|
||||
uint32_t num_samples, loop_start = 0, loop_end = 0;
|
||||
off_t start_offset;
|
||||
|
||||
|
||||
/* check extension, case insensitive */
|
||||
if (!check_extensions(streamFile,"snu"))
|
||||
goto fail;
|
||||
|
||||
/* check header */
|
||||
//if ((read_32bitBE(0x00,streamFile) & 0x00FFFF00 != 0x00000000) && (read_32bitBE(0x0c,streamFile) != 0x00000000))
|
||||
// goto fail;
|
||||
/* 0x00: related to sample rate?, 0x02: always 0?, 0x03: related to channels? (usually match but may be 0) */
|
||||
/* 0x04: some size, maybe >>2 ~= number of 0x4c frames (BE/LE depending on platform) */
|
||||
/* 0x08: always 0x20? (also BE/LE), 0x0c: always 0? */
|
||||
|
||||
|
||||
start_offset = 0x20; /* first block */
|
||||
|
||||
codec = read_8bit(0x10,streamFile);
|
||||
channel_config = read_8bit(0x11,streamFile);
|
||||
sample_rate = (uint16_t)read_16bitBE(0x12,streamFile);
|
||||
flags = (uint8_t)read_8bit(0x14,streamFile); /* upper nibble only? */
|
||||
num_samples = (uint32_t)read_32bitBE(0x14,streamFile) & 0x00FFFFFF;
|
||||
/* 0x18: null?, 0x1c: null? */
|
||||
|
||||
if (flags != 0x60 && flags != 0x40) {
|
||||
VGM_LOG("EA SNS: unknown flag\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#if 0
|
||||
//todo not working ok with blocks
|
||||
if (flags & 0x60) { /* full loop, seen in ambient tracks */
|
||||
loop_flag = 1;
|
||||
loop_start = 0;
|
||||
loop_end = num_samples;
|
||||
}
|
||||
#endif
|
||||
|
||||
//channel_count = (channel_config >> 2) + 1; //todo test
|
||||
/* 01/02/03 = 1 ch?, 05/06/07 = 2/3 ch?, 0d/0e/0f = 4/5 ch?, 14/15/16/17 = 6/7 ch?, 1d/1e/1f = 8 ch? */
|
||||
switch(channel_config) {
|
||||
case 0x00: channel_count = 1; break;
|
||||
case 0x04: channel_count = 2; break;
|
||||
case 0x0c: channel_count = 4; break;
|
||||
case 0x14: channel_count = 6; break;
|
||||
case 0x1c: channel_count = 8; break;
|
||||
default:
|
||||
VGM_LOG("EA SNU: unknown channel config 0x%02x\n", channel_config);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(channel_count,loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->sample_rate = sample_rate;
|
||||
vgmstream->num_samples = num_samples;
|
||||
vgmstream->loop_start_sample = loop_start;
|
||||
vgmstream->loop_end_sample = loop_end;
|
||||
|
||||
vgmstream->meta_type = meta_EA_SNU;
|
||||
vgmstream->layout_type = layout_ea_sns_blocked;
|
||||
|
||||
switch(codec) {
|
||||
case 0x04: /* "Xas1": EA-XAS (Dead Space) */
|
||||
vgmstream->coding_type = coding_EA_XAS;
|
||||
break;
|
||||
|
||||
case 0x00: /* "NONE" */
|
||||
case 0x01: /* not used? */
|
||||
case 0x02: /* "P6B0": PCM16BE */
|
||||
case 0x03: /* "EXm0": EA-XMA */
|
||||
case 0x05: /* "EL31": EALayer3 v1 b (with PCM blocks in normal EA-frames?) */
|
||||
case 0x06: /* "EL32P": EALayer3 v2 "P" */
|
||||
case 0x07: /* "EL32S": EALayer3 v2 "S" */
|
||||
case 0x09: /* EASpeex? */
|
||||
case 0x0c: /* EAOpus? */
|
||||
case 0x0e: /* XAS variant? */
|
||||
case 0x0f: /* EALayer3 variant? */
|
||||
/* also 0x1n variations, used in other headers */
|
||||
default:
|
||||
VGM_LOG("EA SNU: unknown codec 0x%02x\n", codec);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
||||
/* open the file for reading by each channel */
|
||||
if (!vgmstream_open_stream(vgmstream,streamFile,start_offset))
|
||||
goto fail;
|
||||
|
||||
ea_sns_block_update(start_offset, vgmstream);
|
||||
|
||||
return vgmstream;
|
||||
|
||||
fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
@ -212,10 +212,13 @@ VGMSTREAM * init_vgmstream_fsb5(STREAMFILE *streamFile) {
|
||||
case 0x04: /* FMOD_SOUND_FORMAT_PCM32 */
|
||||
goto fail;
|
||||
|
||||
case 0x05: /* FMOD_SOUND_FORMAT_PCMFLOAT */
|
||||
goto fail;
|
||||
case 0x05: /* FMOD_SOUND_FORMAT_PCMFLOAT [Anima - Gate of Memories (PC)]*/
|
||||
vgmstream->coding_type = coding_PCMFLOAT;
|
||||
vgmstream->layout_type = (ChannelCount == 1) ? layout_none : layout_interleave;
|
||||
vgmstream->interleave_block_size = 0x04;
|
||||
break;
|
||||
|
||||
case 0x06: /* FMOD_SOUND_FORMAT_GCADPCM */
|
||||
case 0x06: /* FMOD_SOUND_FORMAT_GCADPCM [Sonic Boom - Fire and Ice (3DS)] */
|
||||
if (ChannelCount == 1) {
|
||||
vgmstream->layout_type = layout_none;
|
||||
} else {
|
||||
|
@ -682,4 +682,6 @@ VGMSTREAM * init_vgmstream_sk_aud(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_stm(STREAMFILE * streamFile);
|
||||
|
||||
VGMSTREAM * init_vgmstream_ea_snu(STREAMFILE * streamFile);
|
||||
|
||||
#endif /*_META_H*/
|
||||
|
@ -38,6 +38,11 @@ void put_32bitBE(uint8_t * buf, int32_t i);
|
||||
/* signed nibbles come up a lot */
|
||||
static int nibble_to_int[16] = {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1};
|
||||
|
||||
static inline int get_nibble_signed(uint8_t n, int upper) {
|
||||
/*return ((n&0x70)-(n&0x80))>>4;*/
|
||||
return nibble_to_int[(n >> (upper?4:0)) & 0x0f];
|
||||
}
|
||||
|
||||
static inline int get_high_nibble_signed(uint8_t n) {
|
||||
/*return ((n&0x70)-(n&0x80))>>4;*/
|
||||
return nibble_to_int[n>>4];
|
||||
|
@ -343,8 +343,8 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
|
||||
init_vgmstream_akb2_multi,
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
init_vgmstream_mp4_aac_ffmpeg,
|
||||
init_vgmstream_bik,
|
||||
#endif
|
||||
init_vgmstream_bik,
|
||||
init_vgmstream_x360_ast,
|
||||
init_vgmstream_wwise,
|
||||
init_vgmstream_ubi_raki,
|
||||
@ -366,6 +366,7 @@ VGMSTREAM * (*init_vgmstream_fcns[])(STREAMFILE *streamFile) = {
|
||||
init_vgmstream_ea_schl_fixed,
|
||||
init_vgmstream_sk_aud,
|
||||
init_vgmstream_stm,
|
||||
init_vgmstream_ea_snu,
|
||||
|
||||
init_vgmstream_txth, /* should go at the end (lower priority) */
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
@ -871,7 +872,7 @@ int32_t get_vgmstream_play_samples(double looptimes, double fadeseconds, double
|
||||
* Most files cut abruply after the loop, but some do have proper endings.
|
||||
* With looptimes = 1 this option should give the same output vs loop disabled */
|
||||
int loop_count = (int)looptimes; /* no half loops allowed */
|
||||
vgmstream->loop_target = loop_count;
|
||||
//vgmstream->loop_target = loop_count; /* handled externally, as this is into-only */
|
||||
return vgmstream->loop_start_sample
|
||||
+ (vgmstream->loop_end_sample - vgmstream->loop_start_sample) * loop_count
|
||||
+ (vgmstream->num_samples - vgmstream->loop_end_sample);
|
||||
@ -931,6 +932,7 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre
|
||||
case layout_ps2_strlr_blocked:
|
||||
case layout_rws_blocked:
|
||||
case layout_hwas_blocked:
|
||||
case layout_ea_sns_blocked:
|
||||
render_vgmstream_blocked(buffer,sample_count,vgmstream);
|
||||
break;
|
||||
case layout_interleave_byte:
|
||||
@ -975,6 +977,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
|
||||
case coding_PCM8_SB_int:
|
||||
case coding_PCM8_U_int:
|
||||
case coding_ULAW:
|
||||
case coding_PCMFLOAT:
|
||||
return 1;
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case coding_ogg_vorbis:
|
||||
case coding_VORBIS_custom:
|
||||
@ -1034,6 +1038,8 @@ int get_vgmstream_samples_per_frame(VGMSTREAM * vgmstream) {
|
||||
return 28;
|
||||
case coding_MAXIS_XA:
|
||||
return 14*vgmstream->channels;
|
||||
case coding_EA_XAS:
|
||||
return 128;
|
||||
case coding_WS:
|
||||
/* only works if output sample size is 8 bit, which always is for WS ADPCM */
|
||||
return vgmstream->ws_output_size;
|
||||
@ -1128,6 +1134,9 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
|
||||
case coding_PCM8_SB_int:
|
||||
case coding_PCM8_U_int:
|
||||
case coding_ULAW:
|
||||
return 1;
|
||||
case coding_PCMFLOAT:
|
||||
return 4;
|
||||
case coding_SDX2:
|
||||
case coding_SDX2_int:
|
||||
case coding_CBD2:
|
||||
@ -1182,6 +1191,8 @@ int get_vgmstream_frame_size(VGMSTREAM * vgmstream) {
|
||||
return 0x0F*vgmstream->channels;
|
||||
case coding_EA_XA_V2:
|
||||
return 1; /* the frame is variant in size (ADPCM frames of 0x0F or PCM frames) */
|
||||
case coding_EA_XAS:
|
||||
return 0x4c*vgmstream->channels;
|
||||
case coding_WS:
|
||||
return vgmstream->current_block_size;
|
||||
case coding_IMA_int:
|
||||
@ -1358,6 +1369,14 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
|
||||
samples_to_do);
|
||||
}
|
||||
break;
|
||||
case coding_PCMFLOAT:
|
||||
for (chan=0;chan<vgmstream->channels;chan++) {
|
||||
decode_pcmfloat(vgmstream, &vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
|
||||
vgmstream->channels,vgmstream->samples_into_block,
|
||||
samples_to_do);
|
||||
}
|
||||
break;
|
||||
|
||||
case coding_NDS_IMA:
|
||||
for (chan=0;chan<vgmstream->channels;chan++) {
|
||||
decode_nds_ima(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
|
||||
@ -1498,6 +1517,13 @@ void decode_vgmstream(VGMSTREAM * vgmstream, int samples_written, int samples_to
|
||||
samples_to_do,chan);
|
||||
}
|
||||
break;
|
||||
case coding_EA_XAS:
|
||||
for (chan=0;chan<vgmstream->channels;chan++) {
|
||||
decode_ea_xas(&vgmstream->ch[chan],buffer+samples_written*vgmstream->channels+chan,
|
||||
vgmstream->channels,vgmstream->samples_into_block,
|
||||
samples_to_do,chan);
|
||||
}
|
||||
break;
|
||||
#ifdef VGM_USE_VORBIS
|
||||
case coding_ogg_vorbis:
|
||||
decode_ogg_vorbis(vgmstream->codec_data,
|
||||
@ -2052,7 +2078,7 @@ void describe_vgmstream(VGMSTREAM * vgmstream, char * desc, int length) {
|
||||
/* only interesting if more than one */
|
||||
if (vgmstream->num_streams > 1) {
|
||||
snprintf(temp,TEMPSIZE,
|
||||
"\nstream number: %d",
|
||||
"\nstream count: %d",
|
||||
vgmstream->num_streams);
|
||||
concatn(length,desc,temp);
|
||||
}
|
||||
|
@ -72,21 +72,23 @@ enum { STREAM_NAME_SIZE = 255 }; /* reasonable max */
|
||||
|
||||
/* The encoding type specifies the format the sound data itself takes */
|
||||
typedef enum {
|
||||
/* 16-bit PCM */
|
||||
/* PCM */
|
||||
coding_PCM16LE, /* little endian 16-bit PCM */
|
||||
coding_PCM16LE_int, /* little endian 16-bit PCM with sample-level interleave */
|
||||
coding_PCM16LE_XOR_int, /* little endian 16-bit PCM with sample-level xor */
|
||||
coding_PCM16BE, /* big endian 16-bit PCM */
|
||||
|
||||
/* 8-bit PCM */
|
||||
coding_PCM8, /* 8-bit PCM */
|
||||
coding_PCM8_int, /* 8-Bit PCM with sample-level interleave */
|
||||
coding_PCM8_U, /* 8-bit PCM, unsigned (0x80 = 0) */
|
||||
coding_PCM8_U_int, /* 8-bit PCM, unsigned (0x80 = 0) with sample-level interleave */
|
||||
coding_PCM8_SB_int, /* 8-bit PCM, sign bit (others are 2's complement) with sample-level interleave */
|
||||
|
||||
coding_ULAW, /* 8-bit u-Law (non-linear PCM) */
|
||||
|
||||
/* 4-bit ADPCM */
|
||||
coding_PCMFLOAT, /* 32 bit float PCM */
|
||||
|
||||
/* ADPCM */
|
||||
coding_CRI_ADX, /* CRI ADX */
|
||||
coding_CRI_ADX_fixed, /* CRI ADX, encoding type 2 with fixed coefficients */
|
||||
coding_CRI_ADX_exp, /* CRI ADX, encoding type 4 with exponential scale */
|
||||
@ -110,6 +112,7 @@ typedef enum {
|
||||
coding_EA_XA_int, /* Electronic Arts EA-XA ADPCM v1 (mono/interleave) */
|
||||
coding_EA_XA_V2, /* Electronic Arts EA-XA ADPCM v2 */
|
||||
coding_MAXIS_XA, /* Maxis EA-XA ADPCM */
|
||||
coding_EA_XAS, /* Electronic Arts EA-XAS ADPCM */
|
||||
|
||||
coding_XBOX, /* XBOX IMA ADPCM */
|
||||
coding_XBOX_int, /* XBOX IMA ADPCM (interleaved) */
|
||||
@ -232,6 +235,7 @@ typedef enum {
|
||||
layout_ps2_strlr_blocked,
|
||||
layout_rws_blocked,
|
||||
layout_hwas_blocked,
|
||||
layout_ea_sns_blocked, /* newest Electronic Arts blocks, found in SNS/SNU/SPS/etc formats */
|
||||
|
||||
/* otherwise odd */
|
||||
layout_acm, /* libacm layout */
|
||||
@ -615,6 +619,8 @@ typedef enum {
|
||||
meta_SK_AUD, /* Silicon Knights .AUD (Eternal Darkness GC) */
|
||||
meta_AHX, /* CRI AHX header */
|
||||
meta_STM, /* Angel Studios/Rockstar San Diego Games */
|
||||
meta_BINK, /* RAD Game Tools BINK audio/video */
|
||||
meta_EA_SNU, /* Electronic Arts SNU (Dead Space) */
|
||||
|
||||
#ifdef VGM_USE_VORBIS
|
||||
meta_OGG_VORBIS, /* Ogg Vorbis */
|
||||
|
@ -292,6 +292,11 @@ int main(int argc, char ** argv) {
|
||||
if (!play && !adxencd && !oggenc && !batchvar) printf("samples to play: %d (%.4lf seconds)\n",len,(double)len/s->sample_rate);
|
||||
fade_samples = fade_seconds * s->sample_rate;
|
||||
|
||||
if (loop_count && fade_ignore) {
|
||||
s->loop_target = (int)loop_count;
|
||||
}
|
||||
|
||||
|
||||
/* slap on a .wav header */
|
||||
if (only_stereo != -1) {
|
||||
make_wav_header((uint8_t*)buf, len, s->sample_rate, 2);
|
||||
|
@ -48,6 +48,7 @@
|
||||
#define DEFAULT_THREAD_PRIORITY 3
|
||||
#define DEFAULT_LOOP_FOREVER 0
|
||||
#define DEFAULT_IGNORE_LOOP 0
|
||||
#define DEFAULT_DISABLE_SUBSONGS 0
|
||||
|
||||
#define FADE_SECONDS_INI_ENTRY "fade_seconds"
|
||||
#define FADE_DELAY_SECONDS_INI_ENTRY "fade_delay"
|
||||
@ -55,6 +56,7 @@
|
||||
#define THREAD_PRIORITY_INI_ENTRY "thread_priority"
|
||||
#define LOOP_FOREVER_INI_ENTRY "loop_forever"
|
||||
#define IGNORE_LOOP_INI_ENTRY "ignore_loop"
|
||||
#define DISABLE_SUBSONGS_INI_ENTRY "disable_subsongs"
|
||||
|
||||
char *priority_strings[] = {"Idle","Lowest","Below Normal","Normal","Above Normal","Highest (not recommended)","Time Critical (not recommended)"};
|
||||
int priority_values[] = {THREAD_PRIORITY_IDLE,THREAD_PRIORITY_LOWEST,THREAD_PRIORITY_BELOW_NORMAL,THREAD_PRIORITY_NORMAL,THREAD_PRIORITY_ABOVE_NORMAL,THREAD_PRIORITY_HIGHEST,THREAD_PRIORITY_TIME_CRITICAL};
|
||||
@ -77,6 +79,7 @@ double loop_count;
|
||||
int thread_priority;
|
||||
int loop_forever;
|
||||
int ignore_loop;
|
||||
int disable_subsongs;
|
||||
|
||||
/* plugin state */
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
@ -95,15 +98,29 @@ in_char lastfn[PATH_LIMIT] = {0}; /* name of the currently playing file */
|
||||
|
||||
/* ************************************* */
|
||||
//todo safe ops
|
||||
|
||||
//todo there must be a better way to handle unicode...
|
||||
#ifdef UNICODE_INPUT_PLUGIN
|
||||
#define wa_strcpy wcscpy
|
||||
#define wa_strncpy wcsncpy
|
||||
#define wa_strcat wcscat
|
||||
#define wa_strlen wcslen
|
||||
#define wa_strchr wcschr
|
||||
#define wa_sscanf swscanf
|
||||
#define wa_snprintf snwprintf
|
||||
#define wa_fileinfo fileinfoW
|
||||
#define wa_IPC_PE_INSERTFILENAME IPC_PE_INSERTFILENAMEW
|
||||
#define wa_L(x) L ##x
|
||||
#else
|
||||
#define wa_strcpy strcpy
|
||||
#define wa_strncpy strncpy
|
||||
#define wa_strcat strcat
|
||||
#define wa_strlen strlen
|
||||
#define wa_strchr strchr
|
||||
#define wa_sscanf sscanf
|
||||
#define wa_snprintf snprintf
|
||||
#define wa_fileinfo fileinfo
|
||||
#define wa_IPC_PE_INSERTFILENAME IPC_PE_INSERTFILENAME
|
||||
#define wa_L(x) x
|
||||
#endif
|
||||
|
||||
/* converts from utf16 to utf8 (if unicode is active) */
|
||||
@ -264,7 +281,7 @@ static STREAMFILE *open_winamp_streamfile_by_wpath(const in_char *wpath) {
|
||||
}
|
||||
|
||||
/* opens vgmstream for winamp */
|
||||
static VGMSTREAM* init_vgmstream_winamp(const in_char *fn) {
|
||||
static VGMSTREAM* init_vgmstream_winamp(const in_char *fn, int stream_index) {
|
||||
VGMSTREAM * vgmstream = NULL;
|
||||
|
||||
//return init_vgmstream(fn);
|
||||
@ -272,6 +289,7 @@ static VGMSTREAM* init_vgmstream_winamp(const in_char *fn) {
|
||||
/* manually init streamfile to pass the stream index */
|
||||
STREAMFILE *streamFile = open_winamp_streamfile_by_wpath(fn); //open_stdio_streamfile(fn);
|
||||
if (streamFile) {
|
||||
streamFile->stream_index = stream_index;
|
||||
vgmstream = init_vgmstream_from_STREAMFILE(streamFile);
|
||||
close_streamfile(streamFile);
|
||||
}
|
||||
@ -281,6 +299,102 @@ static VGMSTREAM* init_vgmstream_winamp(const in_char *fn) {
|
||||
|
||||
/* ************************************* */
|
||||
|
||||
/* makes a modified filename, suitable to pass parameters around */
|
||||
static void make_fn_subsong(in_char * dst, int dst_size, const in_char * filename, int stream_index) {
|
||||
/* Follows "(file)(config)(ext)". Winamp needs to "see" (ext) to validate, and file goes first so relative
|
||||
* files work in M3Us (path is added). Protocols a la "vgmstream://(config)(file)" work but don't get full paths. */
|
||||
wa_snprintf(dst,dst_size, wa_L("%s|$s=%i|.vgmstream"), filename, stream_index);
|
||||
}
|
||||
|
||||
/* unpacks the subsongs by adding entries to the playlist */
|
||||
static int split_subsongs(const in_char * filename, int stream_index, VGMSTREAM *vgmstream) {
|
||||
int i, playlist_index;
|
||||
HWND hPlaylistWindow;
|
||||
|
||||
|
||||
if (disable_subsongs || vgmstream->num_streams <= 1 || (vgmstream->num_streams > 1 && stream_index > 0))
|
||||
return 0; /* no split if no subsongs or playing a subsong */
|
||||
|
||||
hPlaylistWindow = (HWND)SendMessage(input_module.hMainWindow, WM_WA_IPC, IPC_GETWND_PE, IPC_GETWND);
|
||||
playlist_index = SendMessage(input_module.hMainWindow,WM_WA_IPC,0,IPC_GETLISTPOS);
|
||||
|
||||
/* The only way to pass info around in Winamp is encoding it into the filename, so a fake name
|
||||
* is created with the index. Then, winamp_Play (and related) intercepts and reads the index. */
|
||||
for (i = 0; i < vgmstream->num_streams; i++) {
|
||||
in_char stream_fn[PATH_LIMIT];
|
||||
|
||||
make_fn_subsong(stream_fn,PATH_LIMIT, filename, (i+1)); /* encode index in filename */
|
||||
|
||||
/* insert at index */
|
||||
{
|
||||
COPYDATASTRUCT cds = {0};
|
||||
wa_fileinfo f;
|
||||
|
||||
wa_strncpy(f.file, stream_fn,MAX_PATH-1);
|
||||
f.file[MAX_PATH-1] = '\0';
|
||||
f.index = playlist_index + (i+1);
|
||||
cds.dwData = wa_IPC_PE_INSERTFILENAME;
|
||||
cds.lpData = (void*)&f;
|
||||
cds.cbData = sizeof(wa_fileinfo);
|
||||
SendMessage(hPlaylistWindow,WM_COPYDATA,0,(LPARAM)&cds);
|
||||
}
|
||||
/* IPC_ENQUEUEFILE can pre-set the title without needing the Playlist handle, but can't insert at index */
|
||||
}
|
||||
|
||||
/* remove current file from the playlist */
|
||||
SendMessage(hPlaylistWindow, WM_WA_IPC, IPC_PE_DELETEINDEX, playlist_index);
|
||||
|
||||
/* autoplay doesn't always advance to the first unpacked track, manually fails too */
|
||||
//SendMessage(input_module.hMainWindow,WM_WA_IPC,playlist_index,IPC_SETPLAYLISTPOS);
|
||||
//SendMessage(input_module.hMainWindow,WM_WA_IPC,0,IPC_STARTPLAY);
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* parses a modified filename ('fakename') extracting tags parameters (NULL tag for first = filename) */
|
||||
static int parse_fn_string(const in_char * fn, const in_char * tag, in_char * dst, int dst_size) {
|
||||
in_char *end;
|
||||
|
||||
end = wa_strchr(fn,'|');
|
||||
if (tag==NULL) {
|
||||
wa_strcpy(dst,fn);
|
||||
if (end)
|
||||
dst[end - fn] = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
//todo actually find + read tags
|
||||
dst[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
static int parse_fn_int(const in_char * fn, const in_char * tag, int * num) {
|
||||
in_char * start = wa_strchr(fn,'|');
|
||||
|
||||
//todo actually find + read tags
|
||||
if (start > 0) {
|
||||
wa_sscanf(start+1, wa_L("$s=%i "), num);
|
||||
return 1;
|
||||
} else {
|
||||
*num = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* try to detect XMPlay, which can't interact with the playlist = no splitting */
|
||||
static int is_xmplay() {
|
||||
if (GetModuleHandle("xmplay.exe"))
|
||||
return 1;
|
||||
if (GetModuleHandle("xmp-wadsp.dll"))
|
||||
return 1;
|
||||
if (GetModuleHandle("xmp-wma.dll"))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ************************************* */
|
||||
|
||||
/* Winamp INI reader */
|
||||
static void GetINIFileName(char * iniFile) {
|
||||
/* if we're running on a newer winamp version that better supports
|
||||
@ -364,12 +478,34 @@ static void build_extension_list() {
|
||||
}
|
||||
|
||||
/* unicode utils */
|
||||
static void copy_title(in_char * dst, int dst_size, const in_char * src) {
|
||||
in_char *p = (in_char*)src + wa_strlen(src); /* find end */
|
||||
while (*p != '\\' && p >= src) /* and find last "\" */
|
||||
p--;
|
||||
p++;
|
||||
wa_strcpy(dst,p); /* copy filename only */
|
||||
static void get_title(in_char * dst, int dst_size, const in_char * fn, VGMSTREAM * infostream) {
|
||||
in_char *basename;
|
||||
in_char buffer[PATH_LIMIT];
|
||||
in_char filename[PATH_LIMIT];
|
||||
int stream_index = 0;
|
||||
|
||||
parse_fn_string(fn, NULL, filename,PATH_LIMIT);
|
||||
parse_fn_int(fn, wa_L("$s"), &stream_index);
|
||||
|
||||
basename = (in_char*)filename + wa_strlen(filename); /* find end */
|
||||
while (*basename != '\\' && basename >= filename) /* and find last "\" */
|
||||
basename--;
|
||||
basename++;
|
||||
wa_strcpy(dst,basename);
|
||||
|
||||
/* show stream subsong number */
|
||||
if (stream_index > 0) {
|
||||
wa_snprintf(buffer,PATH_LIMIT, wa_L("#%i"), stream_index);
|
||||
wa_strcat(dst,buffer);
|
||||
}
|
||||
|
||||
/* show name, but not for the base stream */
|
||||
if (infostream && infostream->stream_name[0] != '\0' && stream_index > 0) {
|
||||
in_char stream_name[PATH_LIMIT];
|
||||
wa_char_to_wchar(stream_name, PATH_LIMIT, infostream->stream_name);
|
||||
wa_snprintf(buffer,PATH_LIMIT, wa_L(" (%s)"), stream_name);
|
||||
wa_strcat(dst,buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/* ***************************************** */
|
||||
@ -433,6 +569,18 @@ void winamp_Init() {
|
||||
ignore_loop = DEFAULT_IGNORE_LOOP;
|
||||
}
|
||||
|
||||
disable_subsongs = GetPrivateProfileInt(CONFIG_APP_NAME,DISABLE_SUBSONGS_INI_ENTRY,DEFAULT_DISABLE_SUBSONGS,iniFile);
|
||||
//if (disable_subsongs < 0) {
|
||||
// sprintf(buf,"%d",DEFAULT_DISABLE_SUBSONGS);
|
||||
// WritePrivateProfileString(CONFIG_APP_NAME,DISABLE_SUBSONGS_INI_ENTRY,buf,iniFile);
|
||||
// disable_subsongs = DEFAULT_DISABLE_SUBSONGS;
|
||||
//}
|
||||
|
||||
/* XMPlay with in_vgmstream doesn't support most IPC_x messages so no playlist manipulation */
|
||||
if (is_xmplay()) {
|
||||
disable_subsongs = 1;
|
||||
}
|
||||
|
||||
/* dynamically make a list of supported extensions */
|
||||
build_extension_list();
|
||||
}
|
||||
@ -449,15 +597,28 @@ int winamp_IsOurFile(const in_char *fn) {
|
||||
/* request to start playing a file */
|
||||
int winamp_Play(const in_char *fn) {
|
||||
int max_latency;
|
||||
in_char filename[PATH_LIMIT];
|
||||
int stream_index = 0;
|
||||
|
||||
if (vgmstream)
|
||||
return 1; // TODO: this should either pop up an error box or close the file
|
||||
|
||||
/* check for info encoded in the filename */
|
||||
parse_fn_string(fn, NULL, filename,PATH_LIMIT);
|
||||
parse_fn_int(fn, wa_L("$s"), &stream_index);
|
||||
|
||||
/* open the stream */
|
||||
vgmstream = init_vgmstream_winamp(fn);
|
||||
vgmstream = init_vgmstream_winamp(filename,stream_index);
|
||||
if (!vgmstream)
|
||||
return 1;
|
||||
|
||||
/* add N subsongs to the playlist, if any */
|
||||
if (split_subsongs(filename, stream_index, vgmstream)) {
|
||||
close_vgmstream(vgmstream);
|
||||
vgmstream = NULL;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* config */
|
||||
if (ignore_loop)
|
||||
vgmstream->loop_flag = 0;
|
||||
@ -569,7 +730,6 @@ void winamp_SetPan(int pan) {
|
||||
int winamp_InfoBox(const in_char *fn, HWND hwnd) {
|
||||
char description[1024] = {0};
|
||||
|
||||
|
||||
concatn(sizeof(description),description,PLUGIN_DESCRIPTION "\n\n");
|
||||
|
||||
if (!fn || !*fn) {
|
||||
@ -582,8 +742,14 @@ int winamp_InfoBox(const in_char *fn, HWND hwnd) {
|
||||
else {
|
||||
/* some other file in playlist given by filename */
|
||||
VGMSTREAM * infostream = NULL;
|
||||
in_char filename[PATH_LIMIT];
|
||||
int stream_index = 0;
|
||||
|
||||
infostream = init_vgmstream_winamp(fn);
|
||||
/* check for info encoded in the filename */
|
||||
parse_fn_string(fn, NULL, filename,PATH_LIMIT);
|
||||
parse_fn_int(fn, wa_L("$s"), &stream_index);
|
||||
|
||||
infostream = init_vgmstream_winamp(filename, stream_index);
|
||||
if (!infostream)
|
||||
return 0;
|
||||
|
||||
@ -607,7 +773,7 @@ void winamp_GetFileInfo(const in_char *fn, in_char *title, int *length_in_ms) {
|
||||
return;
|
||||
|
||||
if (title) {
|
||||
copy_title(title,GETFILEINFO_TITLE_LENGTH, lastfn);
|
||||
get_title(title,GETFILEINFO_TITLE_LENGTH, lastfn, vgmstream);
|
||||
}
|
||||
|
||||
if (length_in_ms) {
|
||||
@ -617,11 +783,18 @@ void winamp_GetFileInfo(const in_char *fn, in_char *title, int *length_in_ms) {
|
||||
else {
|
||||
/* some other file in playlist given by filename */
|
||||
VGMSTREAM * infostream = NULL;
|
||||
in_char filename[PATH_LIMIT];
|
||||
int stream_index = 0;
|
||||
|
||||
infostream = init_vgmstream_winamp(fn);
|
||||
/* check for info encoded in the filename */
|
||||
parse_fn_string(fn, NULL, filename,PATH_LIMIT);
|
||||
parse_fn_int(fn, wa_L("$s"), &stream_index);
|
||||
|
||||
infostream = init_vgmstream_winamp(filename, stream_index);
|
||||
if (!infostream) return;
|
||||
|
||||
if (title) {
|
||||
copy_title(title,GETFILEINFO_TITLE_LENGTH, fn);
|
||||
get_title(title,GETFILEINFO_TITLE_LENGTH, fn, infostream);
|
||||
}
|
||||
|
||||
if (length_in_ms) {
|
||||
@ -772,6 +945,9 @@ INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara
|
||||
else
|
||||
CheckDlgButton(hDlg,IDC_LOOP_NORMALLY,BST_CHECKED);
|
||||
|
||||
if (disable_subsongs)
|
||||
CheckDlgButton(hDlg,IDC_DISABLE_SUBSONGS,BST_CHECKED);
|
||||
|
||||
break;
|
||||
case WM_COMMAND:
|
||||
switch (GET_WM_COMMAND_ID(wParam, lParam)) {
|
||||
@ -840,6 +1016,10 @@ INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara
|
||||
ignore_loop = (IsDlgButtonChecked(hDlg,IDC_IGNORE_LOOP) == BST_CHECKED);
|
||||
sprintf(buf,"%d",ignore_loop);
|
||||
WritePrivateProfileString(CONFIG_APP_NAME,IGNORE_LOOP_INI_ENTRY,buf,iniFile);
|
||||
|
||||
disable_subsongs = (IsDlgButtonChecked(hDlg,IDC_DISABLE_SUBSONGS) == BST_CHECKED);
|
||||
sprintf(buf,"%d",disable_subsongs);
|
||||
WritePrivateProfileString(CONFIG_APP_NAME,DISABLE_SUBSONGS_INI_ENTRY,buf,iniFile);
|
||||
}
|
||||
|
||||
EndDialog(hDlg,TRUE);
|
||||
@ -866,6 +1046,8 @@ INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara
|
||||
CheckDlgButton(hDlg,IDC_LOOP_FOREVER,BST_UNCHECKED);
|
||||
CheckDlgButton(hDlg,IDC_IGNORE_LOOP,BST_UNCHECKED);
|
||||
CheckDlgButton(hDlg,IDC_LOOP_NORMALLY,BST_CHECKED);
|
||||
|
||||
CheckDlgButton(hDlg,IDC_DISABLE_SUBSONGS,BST_UNCHECKED);
|
||||
break;
|
||||
default:
|
||||
return FALSE;
|
||||
|
@ -8,3 +8,4 @@
|
||||
#define IDC_THREAD_PRIORITY_SLIDER 1006
|
||||
#define IDC_THREAD_PRIORITY_TEXT 1007
|
||||
#define IDC_DEFAULT_BUTTON 1008
|
||||
#define IDC_DISABLE_SUBSONGS 1009
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include <windows.h>
|
||||
#define IDC_STATIC -1
|
||||
|
||||
IDD_CONFIG DIALOGEX 0, 0, 187, 144
|
||||
IDD_CONFIG DIALOGEX 0, 0, 187, 164
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
CAPTION "in_vgmstream configuration"
|
||||
FONT 8, "MS Sans Serif", 0, 0, 0x0
|
||||
@ -22,8 +22,9 @@ BEGIN
|
||||
CONTROL "Loop normally",IDC_LOOP_NORMALLY,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,57,77,10
|
||||
CONTROL "Loop forever",IDC_LOOP_FOREVER,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,70,77,10
|
||||
CONTROL "Ignore looping",IDC_IGNORE_LOOP,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,7,83,77,10
|
||||
LTEXT "Thread Priority",IDC_STATIC,21,99,46,8
|
||||
CONTROL "Slider1",IDC_THREAD_PRIORITY_SLIDER,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,7,107,77,10
|
||||
CTEXT "DATARIFIC",IDC_THREAD_PRIORITY_TEXT,7,120,77,18
|
||||
CONTROL "Disable subsongs",IDC_DISABLE_SUBSONGS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,99,87,10
|
||||
LTEXT "Thread Priority",IDC_STATIC,21,119,46,8
|
||||
CONTROL "Slider1",IDC_THREAD_PRIORITY_SLIDER,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,7,127,77,10
|
||||
CTEXT "DATARIFIC",IDC_THREAD_PRIORITY_TEXT,7,140,77,18
|
||||
END
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user