Merge pull request #222 from bnnm/layers-xwb-etc

Layers, xwb, etc
This commit is contained in:
Christopher Snowhill 2018-05-05 15:46:33 -07:00 committed by GitHub
commit 871671b5b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 220 additions and 67 deletions

View File

@ -82,7 +82,7 @@ void block_update_ea_swvr(off_t block_offset, VGMSTREAM * vgmstream) {
break;
default: /* ignore, 0 samples */
VGM_LOG("EA SWVR: ignored 0x%08x at 0x%lx\n", block_id, block_offset);
//;VGM_LOG("EA SWVR: ignored 0x%08x at 0x%lx\n", block_id, block_offset);
break;
}

View File

@ -55,6 +55,9 @@ VGMSTREAM * init_vgmstream_ea_swvr(STREAMFILE *streamFile) {
else if (read_32bit(start_offset+0x10, streamFile) == 0x53484452) /* "SHDR" (Future Cop PC) */
start_offset += read_32bit(start_offset+0x04, streamFile);
if (read_32bit(start_offset+0x00, streamFile) == 0x46494C4C) /* "FILL" (Freekstyle) */
start_offset += read_32bit(start_offset+0x04, streamFile);
total_subsongs = 1;
block_id = read_32bit(start_offset, streamFile);
@ -65,7 +68,7 @@ VGMSTREAM * init_vgmstream_ea_swvr(STREAMFILE *streamFile) {
coding = coding_PSX;
if (read_16bit(start_offset+0x1a, streamFile) == 0x0024) {
total_subsongs = read_32bit(start_offset+0x0c, streamFile)+1;
sample_rate = 24000;
sample_rate = 22050;
}
else {
sample_rate = 14008;
@ -75,7 +78,7 @@ VGMSTREAM * init_vgmstream_ea_swvr(STREAMFILE *streamFile) {
case 0x56414742: /* "VAGB" */
coding = coding_PSX;
if (read_16bit(start_offset+0x1a, streamFile) == 0x6400) {
sample_rate = 24000;
sample_rate = 22050;
}
else {
sample_rate = 14008;
@ -85,13 +88,13 @@ VGMSTREAM * init_vgmstream_ea_swvr(STREAMFILE *streamFile) {
case 0x4453504D: /* "DSPM" */
coding = coding_NGC_DSP;
total_subsongs = read_32bit(start_offset+0x0c, streamFile)+1;
sample_rate = 24000;
sample_rate = 22050;
channel_count = 2;
break;
case 0x44535042: /* "DSPB" */
coding = coding_NGC_DSP;
channel_count = 1;
sample_rate = 24000;
sample_rate = 22050;
break;
case 0x4D534943: /* "MSIC" */
coding = coding_PCM8_U_int;

View File

@ -673,6 +673,7 @@ VGMSTREAM * init_vgmstream_opus_std(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_opus_n1(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_opus_capcom(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_opus_nop(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_opus_shinen(STREAMFILE * streamFile);
VGMSTREAM * init_vgmstream_pc_al2(STREAMFILE * streamFile);

View File

@ -233,3 +233,29 @@ VGMSTREAM * init_vgmstream_opus_nop(STREAMFILE *streamFile) {
fail:
return NULL;
}
/* Shin'en variation [Fast RMX (Switch)] */
VGMSTREAM * init_vgmstream_opus_shinen(STREAMFILE *streamFile) {
off_t offset = 0;
int num_samples = 0, loop_start = 0, loop_end = 0;
/* checks */
if ( !check_extensions(streamFile,"opus"))
goto fail;
if (read_32bitBE(0x08,streamFile) != 0x01000080)
goto fail;
offset = 0x08;
num_samples = 0;
loop_start = read_32bitLE(0x00,streamFile);
loop_end = read_32bitLE(0x04,streamFile); /* 0 if no loop */
if (loop_start > loop_end)
goto fail; /* just in case */
return init_vgmstream_opus(streamFile, meta_OPUS, offset, num_samples,loop_start,loop_end);
fail:
return NULL;
}

View File

@ -18,6 +18,8 @@ typedef struct {
size_t loop_start_segment;
size_t loop_end_segment;
size_t is_layered;
} txtp_header;
static txtp_header* parse_txtp(STREAMFILE* streamFile);
@ -28,7 +30,9 @@ static void clean_txtp(txtp_header* txtp);
VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
VGMSTREAM * vgmstream = NULL;
txtp_header* txtp = NULL;
segmented_layout_data *data = NULL;
segmented_layout_data *data_s = NULL;
layered_layout_data * data_l = NULL;
int i;
/* checks */
@ -56,16 +60,14 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
vgmstream->channel_mask = txtp->entry[0].channel_mask;
}
else {
/* multi file */
int num_samples, loop_start_sample = 0, loop_end_sample = 0;
int i;
int loop_flag, channel_count;
else if (txtp->is_layered) {
/* layered multi file */
int channel_count = 0, loop_flag;
/* init layout */
data = init_layout_segmented(txtp->entry_count);
if (!data) goto fail;
data_l = init_layout_layered(txtp->entry_count);
if (!data_l) goto fail;
/* open each segment subfile */
for (i = 0; i < txtp->entry_count; i++) {
@ -73,15 +75,61 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
if (!temp_streamFile) goto fail;
temp_streamFile->stream_index = txtp->entry[i].subsong;
data->segments[i] = init_vgmstream_from_STREAMFILE(temp_streamFile);
data_l->layers[i] = init_vgmstream_from_STREAMFILE(temp_streamFile);
close_streamfile(temp_streamFile);
if (!data->segments[i]) goto fail;
if (!data_l->layers[i]) goto fail;
data->segments[i]->channel_mask = txtp->entry[0].channel_mask;
channel_count += data_l->layers[i]->channels;
}
/* setup layered VGMSTREAMs */
if (!setup_layout_layered(data_l))
goto fail;
loop_flag = data_l->layers[0]->loop_flag;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = data_l->layers[0]->sample_rate;
vgmstream->num_samples = data_l->layers[0]->num_samples;
vgmstream->loop_start_sample = data_l->layers[0]->loop_start_sample;
vgmstream->loop_end_sample = data_l->layers[0]->loop_end_sample;
vgmstream->meta_type = meta_TXTP;
vgmstream->coding_type = data_l->layers[0]->coding_type;
vgmstream->layout_type = layout_layered;
vgmstream->channel_mask = txtp->entry[0].channel_mask;
vgmstream->layout_data = data_l;
}
else {
/* segmented multi file */
int num_samples, loop_start_sample = 0, loop_end_sample = 0;
int loop_flag, channel_count;
/* init layout */
data_s = init_layout_segmented(txtp->entry_count);
if (!data_s) goto fail;
/* open each segment subfile */
for (i = 0; i < txtp->entry_count; i++) {
STREAMFILE* temp_streamFile = open_streamfile_by_filename(streamFile, txtp->entry[i].filename);
if (!temp_streamFile) goto fail;
temp_streamFile->stream_index = txtp->entry[i].subsong;
data_s->segments[i] = init_vgmstream_from_STREAMFILE(temp_streamFile);
close_streamfile(temp_streamFile);
if (!data_s->segments[i]) goto fail;
data_s->segments[i]->channel_mask = txtp->entry[0].channel_mask;
}
/* setup segmented VGMSTREAMs */
if (!setup_layout_segmented(data))
if (!setup_layout_segmented(data_s))
goto fail;
/* get looping and samples */
@ -89,37 +137,37 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
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->segment_count; i++) {
for (i = 0; i < data_s->segment_count; i++) {
if (loop_flag && txtp->loop_start_segment == i+1) {
loop_start_sample = num_samples;
}
num_samples += data->segments[i]->num_samples;
num_samples += data_s->segments[i]->num_samples;
if (loop_flag && txtp->loop_end_segment == i+1) {
loop_end_sample = num_samples;
}
}
channel_count = data->segments[0]->channels;
channel_count = data_s->segments[0]->channels;
/* build the VGMSTREAM */
vgmstream = allocate_vgmstream(channel_count,loop_flag);
if (!vgmstream) goto fail;
vgmstream->sample_rate = data->segments[0]->sample_rate;
vgmstream->sample_rate = data_s->segments[0]->sample_rate;
vgmstream->num_samples = num_samples;
vgmstream->loop_start_sample = loop_start_sample;
vgmstream->loop_end_sample = loop_end_sample;
vgmstream->meta_type = meta_TXTP;
vgmstream->coding_type = data->segments[0]->coding_type;
vgmstream->coding_type = data_s->segments[0]->coding_type;
vgmstream->layout_type = layout_segmented;
vgmstream->layout_data = data;
vgmstream->layout_data = data_s;
if (loop_flag)
data->loop_segment = txtp->loop_start_segment-1;
data_s->loop_segment = txtp->loop_start_segment-1;
}
@ -129,7 +177,8 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) {
fail:
clean_txtp(txtp);
close_vgmstream(vgmstream);
free_layout_segmented(data);
free_layout_segmented(data_s);
free_layout_layered(data_l);
return NULL;
}
@ -267,6 +316,14 @@ static int parse_keyval(txtp_header * txtp, const char * key, const char * val)
else if (0==strcmp(key,"loop_end_segment")) {
if (!parse_num(val, &txtp->loop_end_segment)) goto fail;
}
if (0==strcmp(key,"mode")) {
if (0==strcmp(val,"layers")) {
txtp->is_layered = 1;
}
else {
goto fail;
}
}
else {
VGM_LOG("TXTP: unknown key=%s, val=%s\n", key,val);
goto fail;

View File

@ -1,6 +1,7 @@
#include "meta.h"
#include "../util.h"
#include "../coding/coding.h"
#include <string.h>
/* most info from XWBtool, xactwb.h, xact2wb.h and xact3wb.h */
@ -25,7 +26,7 @@ static const int wma_block_align_index[17] = {
};
typedef enum { PCM, XBOX_ADPCM, MS_ADPCM, XMA1, XMA2, WMA, XWMA, ATRAC3, OGG } xact_codec;
typedef enum { PCM, XBOX_ADPCM, MS_ADPCM, XMA1, XMA2, WMA, XWMA, ATRAC3, OGG, DSP } xact_codec;
typedef struct {
int little_endian;
int version;
@ -311,14 +312,19 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
xwb.loop_end = 0;
}
/* Stardew Valley (Switch) format hijack, with full interleaved DSPs (including headers) */
if (xwb.version == XACT3_0_MAX && xwb.codec == XMA2
&& xwb.bits_per_sample == 0x01 && xwb.block_align == 0x04 && xwb.data_size == 0x55951c1c) {
xwb.codec = DSP;
}
/* test loop after the above fixes */
xwb.loop_flag = (xwb.loop_end > 0 || xwb.loop_end_sample > xwb.loop_start)
&& !(xwb.entry_flags & WAVEBANKENTRY_FLAGS_IGNORELOOP);
if (xwb.codec != OGG) {
/* for Oddworld OGG the data_size value is size of uncompressed bytes instead */
/* some BlazBlue Centralfiction songs have padding after data size (maybe wrong rip?) */
/* Oddworld OGG the data_size value is size of uncompressed bytes instead; DSP uses some id/config as value */
if (xwb.codec != OGG && xwb.codec != DSP) {
/* some low-q rips don't remove padding, relax validation a bit */
if (xwb.data_offset + xwb.data_size > get_streamfile_size(streamFile))
goto fail;
}
@ -462,7 +468,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
break;
}
case XWMA: { /* WMAudio2 (WMA v2): BlazBlue (X360), WMAudio3 (WMA Pro): ? */
case XWMA: { /* WMAudio2 (WMA v2): BlazBlue (X360), WMAudio3 (WMA Pro): Bullet Witch (PC) voices */
uint8_t buf[100];
int bytes, bps_index, block_align, block_index, avg_bps, wma_codec;
@ -485,7 +491,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
break;
}
case ATRAC3: { /* Techland PS3 extension: Sniper Ghost Warrior (PS3) */
case ATRAC3: { /* Techland PS3 extension [Sniper Ghost Warrior (PS3)] */
uint8_t buf[200];
int bytes;
@ -503,7 +509,7 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
break;
}
case OGG: { /* Oddworld: Strangers Wrath iOS/Android extension */
case OGG: { /* Oddworld: Strangers Wrath (iOS/Android) extension */
vgmstream->codec_data = init_ffmpeg_offset(streamFile, xwb.stream_offset, xwb.stream_size);
if ( !vgmstream->codec_data ) goto fail;
vgmstream->coding_type = coding_FFmpeg;
@ -513,6 +519,17 @@ VGMSTREAM * init_vgmstream_xwb(STREAMFILE *streamFile) {
#endif
case DSP: { /* Stardew Valley (Switch) extension */
vgmstream->coding_type = coding_NGC_DSP;
vgmstream->layout_type = layout_interleave;
vgmstream->interleave_block_size = xwb.stream_size / xwb.channels;
dsp_read_coefs(vgmstream,streamFile,xwb.stream_offset + 0x1c,vgmstream->interleave_block_size,!xwb.little_endian);
dsp_read_hist (vgmstream,streamFile,xwb.stream_offset + 0x3c,vgmstream->interleave_block_size,!xwb.little_endian);
xwb.stream_offset += 0x60; /* skip DSP header */
break;
}
default:
goto fail;
}
@ -591,7 +608,7 @@ typedef struct {
/* try to find the stream name in a companion XSB file, a comically complex cue format. */
static int get_xsb_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamXwb) {
static int get_xsb_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamXwb, char* filename) {
STREAMFILE *streamFile = NULL;
int i,j, start_sound, cfg__start_sound = 0, cfg__selected_wavebank = 0;
int xsb_version;
@ -601,7 +618,10 @@ static int get_xsb_name(char * buf, size_t maxsize, int target_subsong, xwb_head
xsb_header xsb = {0};
streamFile = open_streamfile_by_ext(streamXwb, "xsb");
if (filename)
streamFile = open_streamfile_by_filename(streamXwb, filename);
else
streamFile = open_streamfile_by_ext(streamXwb, "xsb");
if (!streamFile) goto fail;
/* check header */
@ -718,8 +738,10 @@ static int get_xsb_name(char * buf, size_t maxsize, int target_subsong, xwb_head
} else {
suboff = size - 0x08;
}
//} else if (flag==0x11) { /* Stardew Valley (Switch) */
// suboff = size; //???
} else {
VGM_LOG("XSB: xsb flag 0x%x at offset 0x%08lx not implemented\n", flag, off);
VGM_LOG("XSB: xsb flag 0x%x (size=%x) at offset 0x%08lx not implemented\n", flag, size, off);
goto fail;
}
}
@ -871,18 +893,34 @@ fail:
static void get_name(char * buf, size_t maxsize, int target_subsong, xwb_header * xwb, STREAMFILE *streamFile) {
int name_found;
char xwb_filename[PATH_LIMIT];
char xsb_filename[PATH_LIMIT];
/* try inside this xwb */
name_found = get_xwb_name(buf, maxsize, target_subsong, xwb, streamFile);
if (name_found) return;
/* try again in external .xsb */
get_xsb_name(buf, maxsize, target_subsong, xwb, streamFile);
/* try again in external .xsb, using a bunch of possible name pairs */
get_streamfile_filename(streamFile,xwb_filename,PATH_LIMIT);
if (strcmp(xwb_filename,"Wave Bank.xwb")==0) {
strcpy(xsb_filename,"Sound Bank.xsb");
}
else if (strcmp(xwb_filename,"UIMusicBank.xwb")==0) {
strcpy(xsb_filename,"UISoundBank.xsb");
}
else {
xsb_filename[0] = '\0';
}
//todo try others: InGameMusic.xwb + ingamemusic.xsb, NB_BGM_m0100_WB.xwb + NB_BGM_m0100_SB.xsb, etc
if (xsb_filename[0] != '\0') {
name_found = get_xsb_name(buf, maxsize, target_subsong, xwb, streamFile, xsb_filename);
if (name_found) return;
}
//todo try again with common names (xwb and xsb often are named slightly differently using a common convention):
// InGameMusic.xwb + ingamemusic.xsb
// UIMusicBank.xwb + UISoundBank.xsb
// NB_BGM_m0100_WB.xwb + NB_BGM_m0100_SB.xsb
// Wave Bank.xwb + Sound Bank.xsb
/* one last time with same name */
get_xsb_name(buf, maxsize, target_subsong, xwb, streamFile, NULL);
}

View File

@ -367,6 +367,7 @@ VGMSTREAM * (*init_vgmstream_functions[])(STREAMFILE *streamFile) = {
init_vgmstream_opus_n1,
init_vgmstream_opus_capcom,
init_vgmstream_opus_nop,
init_vgmstream_opus_shinen,
init_vgmstream_pc_al2,
init_vgmstream_pc_ast,
init_vgmstream_naac,

View File

@ -56,7 +56,7 @@ typedef struct {
int loop_forever;
int ignore_loop;
int disable_subsongs;
int downmix;
int downmix_channels;
} winamp_config;
winamp_config config;
@ -318,7 +318,7 @@ static void cfg_char_to_wchar(TCHAR *wdst, size_t wdstsize, const char *src) {
#define DEFAULT_LOOP_FOREVER 0
#define DEFAULT_IGNORE_LOOP 0
#define DEFAULT_DISABLE_SUBSONGS 0
#define DEFAULT_DOWNMIX 0
#define DEFAULT_DOWNMIX_CHANNELS 0
#define INI_ENTRY_FADE_SECONDS TEXT("fade_seconds")
#define INI_ENTRY_FADE_DELAY_SECONDS TEXT("fade_delay")
@ -327,7 +327,7 @@ static void cfg_char_to_wchar(TCHAR *wdst, size_t wdstsize, const char *src) {
#define INI_ENTRY_LOOP_FOREVER TEXT("loop_forever")
#define INI_ENTRY_IGNORE_LOOP TEXT("ignore_loop")
#define INI_ENTRY_DISABLE_SUBSONGS TEXT("disable_subsongs")
#define INI_ENTRY_DOWNMIX TEXT("downmix")
#define INI_ENTRY_DOWNMIX_CHANNELS TEXT("downmix_channels")
TCHAR *priority_strings[] = {
TEXT("Idle"),
@ -434,12 +434,13 @@ static void load_config() {
// config.disable_subsongs = DEFAULT_DISABLE_SUBSONGS;
//}
config.downmix = GetPrivateProfileInt(CONFIG_APP_NAME,INI_ENTRY_DOWNMIX,DEFAULT_DOWNMIX,iniFile);
//if (config.downmix < 0) { //unneeded?
// sprintf(buf, TEXT("%d"),DEFAULT_DOWNMIX);
// WritePrivateProfileString(CONFIG_APP_NAME,INI_ENTRY_DOWNMIX,buf,iniFile);
// config.downmix = DEFAULT_DOWNMIX;
//}
config.downmix_channels = GetPrivateProfileInt(CONFIG_APP_NAME,INI_ENTRY_DOWNMIX_CHANNELS,DEFAULT_DOWNMIX_CHANNELS,iniFile);
if (config.downmix_channels < 0) {
cfg_sprintf(buf, TEXT("%d"),DEFAULT_DOWNMIX_CHANNELS);
WritePrivateProfileString(CONFIG_APP_NAME,INI_ENTRY_DOWNMIX_CHANNELS,buf,iniFile);
config.downmix_channels = DEFAULT_DOWNMIX_CHANNELS;
}
}
/* config dialog handler */
@ -487,8 +488,8 @@ INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara
if (config.disable_subsongs)
CheckDlgButton(hDlg,IDC_DISABLE_SUBSONGS,BST_CHECKED);
if (config.downmix)
CheckDlgButton(hDlg,IDC_DOWNMIX,BST_CHECKED);
cfg_sprintf(buf, TEXT("%d"),config.downmix_channels);
SetDlgItemText(hDlg,IDC_DOWNMIX_CHANNELS,buf);
break;
@ -499,6 +500,7 @@ INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara
double temp_fade_seconds;
double temp_fade_delay_seconds;
double temp_loop_count;
int temp_downmix_channels;
int consumed, res;
GetDlgItemText(hDlg,IDC_FADE_SECONDS,buf,buf_size);
@ -531,6 +533,17 @@ INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara
break;
}
GetDlgItemText(hDlg,IDC_DOWNMIX_CHANNELS,buf,buf_size);
res = cfg_sscanf(buf, TEXT("%d%n"),&temp_downmix_channels,&consumed);
if (res < 1 || consumed != cfg_strlen(buf) || temp_downmix_channels < 0) {
MessageBox(hDlg,
TEXT("Invalid value for Downmix Channels\n")
TEXT("Must be a number greater than or equal to zero"),
TEXT("Error"),MB_OK|MB_ICONERROR);
break;
}
GetINIFileName(iniFile);
config.thread_priority = mypri;
@ -561,9 +574,13 @@ INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara
cfg_sprintf(buf, TEXT("%d"),config.disable_subsongs);
WritePrivateProfileString(CONFIG_APP_NAME,INI_ENTRY_DISABLE_SUBSONGS,buf,iniFile);
config.downmix = (IsDlgButtonChecked(hDlg,IDC_DOWNMIX) == BST_CHECKED);
cfg_sprintf(buf, TEXT("%d"),config.downmix);
WritePrivateProfileString(CONFIG_APP_NAME,INI_ENTRY_DOWNMIX,buf,iniFile);
config.loop_count = temp_loop_count;
cfg_sprintf(buf, TEXT("%.2lf"),config.loop_count);
WritePrivateProfileString(CONFIG_APP_NAME,INI_ENTRY_LOOP_COUNT,buf,iniFile);
config.downmix_channels = temp_downmix_channels;
cfg_sprintf(buf, TEXT("%d"),config.downmix_channels);
WritePrivateProfileString(CONFIG_APP_NAME,INI_ENTRY_DOWNMIX_CHANNELS,buf,iniFile);
}
EndDialog(hDlg,TRUE);
@ -593,7 +610,7 @@ INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lPara
CheckDlgButton(hDlg,IDC_LOOP_NORMALLY,BST_CHECKED);
CheckDlgButton(hDlg,IDC_DISABLE_SUBSONGS,BST_UNCHECKED);
CheckDlgButton(hDlg,IDC_DOWNMIX,BST_UNCHECKED);
SetDlgItemText(hDlg,IDC_DOWNMIX_CHANNELS,DEFAULT_DOWNMIX_CHANNELS);
break;
default:
@ -887,8 +904,8 @@ int winamp_Play(const in_char *fn) {
vgmstream->loop_flag = 0;
output_channels = vgmstream->channels;
if (config.downmix)
output_channels = vgmstream->channels > 2 ? 2 : vgmstream->channels;
if (config.downmix_channels > 0 && config.downmix_channels < vgmstream->channels)
output_channels = config.downmix_channels;
/* save original name */
@ -1180,19 +1197,28 @@ DWORD WINAPI __stdcall decode(void *arg) {
}
/* downmix enabled (useful when the stream's channels are too much for Winamp's output) */
if (config.downmix) {
if (config.downmix_channels > 0 && config.downmix_channels < vgmstream->channels) {
short temp_buffer[(576*2) * 2];
int s, ch;
/* just copy the first channels for now */
for (s = 0; s < samples_to_do; s++) {
for (ch = 0; ch < output_channels; ch++) {
temp_buffer[s*output_channels + ch] = sample_buffer[s*vgmstream->channels + ch];
/* copy channels up to max */
for (ch = 0; ch < config.downmix_channels; ch++) {
temp_buffer[s*config.downmix_channels + ch] = sample_buffer[s*vgmstream->channels + ch];
}
/* then mix the rest */
for (ch = config.downmix_channels; ch < vgmstream->channels; ch++) {
int downmix_ch = ch % config.downmix_channels;
int new_sample = ((int)temp_buffer[s*config.downmix_channels + downmix_ch] + (int)sample_buffer[s*vgmstream->channels + ch]);
new_sample = (int)(new_sample * 0.7); /* limit clipping without removing too much loudness... hopefully */
if (new_sample > 32767) new_sample = 32767;
else if (new_sample < -32768) new_sample = -32768;
temp_buffer[s*config.downmix_channels + downmix_ch] = (short)new_sample;
}
}
/* copy back to global buffer */
memcpy(sample_buffer,temp_buffer, samples_to_do*output_channels*sizeof(short));
/* copy back to global buffer... in case of multithreading stuff? */
memcpy(sample_buffer,temp_buffer, samples_to_do*config.downmix_channels*sizeof(short));
}
/* output samples */

View File

@ -9,4 +9,4 @@
#define IDC_THREAD_PRIORITY_TEXT 1007
#define IDC_DEFAULT_BUTTON 1008
#define IDC_DISABLE_SUBSONGS 1009
#define IDC_DOWNMIX 1010
#define IDC_DOWNMIX_CHANNELS 1011

View File

@ -24,7 +24,8 @@ BEGIN
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
CONTROL "Downmix",IDC_DOWNMIX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,112,87,10
LTEXT "Downmix",IDC_STATIC,7,115,48,12
EDITTEXT IDC_DOWNMIX_CHANNELS,52,112,37,14,ES_AUTOHSCROLL
LTEXT "Thread Priority",IDC_STATIC,21,132,46,8
CONTROL "Slider1",IDC_THREAD_PRIORITY_SLIDER,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,7,140,77,10
CTEXT "DATARIFIC",IDC_THREAD_PRIORITY_TEXT,7,153,77,18