diff --git a/src/meta/txtp.c b/src/meta/txtp.c index a0a8dcf2..995bedf6 100644 --- a/src/meta/txtp.c +++ b/src/meta/txtp.c @@ -9,6 +9,8 @@ typedef struct { char filename[TXT_LINE_MAX]; int subsong; uint32_t channel_mask; + int channel_mappings_on; + int channel_mappings[32]; } txtp_entry; typedef struct { @@ -59,6 +61,13 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { if (!vgmstream) goto fail; vgmstream->channel_mask = txtp->entry[0].channel_mask; + + vgmstream->channel_mappings_on = txtp->entry[0].channel_mappings_on; + if(vgmstream->channel_mappings_on) { + for (i = 0; i < 32; i++) { + vgmstream->channel_mappings[i] = txtp->entry[0].channel_mappings[i]; + } + } } else if (txtp->is_layered) { /* layered multi file */ @@ -103,6 +112,13 @@ VGMSTREAM * init_vgmstream_txtp(STREAMFILE *streamFile) { vgmstream->channel_mask = txtp->entry[0].channel_mask; + vgmstream->channel_mappings_on = txtp->entry[0].channel_mappings_on; + if (vgmstream->channel_mappings_on) { + for (i = 0; i < 32; i++) { + vgmstream->channel_mappings[i] = txtp->entry[0].channel_mappings[i]; + } + } + vgmstream->layout_data = data_l; } else { @@ -186,6 +202,8 @@ fail: static int add_filename(txtp_header * txtp, char *filename) { int i; uint32_t channel_mask = 0; + int channel_mappings_on = 0; + int channel_mappings[32] = {0}; size_t range_start, range_end; //;VGM_LOG("TXTP: filename=%s\n", filename); @@ -193,7 +211,9 @@ static int add_filename(txtp_header * txtp, char *filename) { /* parse config: * - file.ext#2 = play subsong 2 * - file.ext#2~10 = play subsongs in 2 to 10 range - * - file.ext#c1,2 = play channels 1,2 */ + * - file.ext#c1,2 = play channels 1,2 + * - file.ext#m1-2,3-4 = swaps channels 1<>2 and 3<>4 + */ { char *config; @@ -213,6 +233,7 @@ static int add_filename(txtp_header * txtp, char *filename) { config[0] = '\0'; config++; + //todo: alt long words, s or number=subsong if (config[0] == 'c') { /* mask channels */ @@ -221,7 +242,7 @@ static int add_filename(txtp_header * txtp, char *filename) { config++; channel_mask = 0; while (sscanf(config, "%d%n", &ch,&n) == 1) { - if (ch > 0 && ch < 32) + if (ch > 0 && ch <= 32) channel_mask |= (1 << (ch-1)); config += n; @@ -231,6 +252,35 @@ static int add_filename(txtp_header * txtp, char *filename) { break; }; } + else if (config[0] == 'm') { + /* channel mappings */ + int n, ch_from = 0, ch_to = 0; + + config++; + channel_mappings_on = 1; + + while (config[0] != '\0') { + if (sscanf(config, "%d%n", &ch_from, &n) != 1) + break; + config += n; + if (config[0]== ',' || config[0]== '-') + config++; + else if (config[0] != '\0') + break; + + if (sscanf(config, "%d%n", &ch_to, &n) != 1) + break; + config += n; + if (config[0]== ',' || config[0]== '-') + config++; + else if (config[0] != '\0') + break; + + if (ch_from > 0 && ch_from <= 32 && ch_to > 0 && ch_to <= 32) { + channel_mappings[ch_from-1] = ch_to-1; + } + } + } else { /* subsong range */ int subsong_start = 0, subsong_end = 0; @@ -287,7 +337,16 @@ static int add_filename(txtp_header * txtp, char *filename) { memset(&txtp->entry[txtp->entry_count],0, sizeof(txtp_entry)); strcpy(txtp->entry[txtp->entry_count].filename, filename); + txtp->entry[txtp->entry_count].channel_mask = channel_mask; + + if (channel_mappings_on) { + int ch; + txtp->entry[txtp->entry_count].channel_mappings_on = channel_mappings_on; + for (ch = 0; ch < 32; ch++) { + txtp->entry[txtp->entry_count].channel_mappings[ch] = channel_mappings[ch]; + } + } txtp->entry[txtp->entry_count].subsong = (i+1); txtp->entry_count++; } diff --git a/src/vgmstream.c b/src/vgmstream.c index 17d06784..a1854854 100644 --- a/src/vgmstream.c +++ b/src/vgmstream.c @@ -959,6 +959,26 @@ void render_vgmstream(sample * buffer, int32_t sample_count, VGMSTREAM * vgmstre } + /* swap channels if set, to create custom channel mappings */ + if (vgmstream->channel_mappings_on) { + int ch_from,ch_to,s; + sample temp; + for (s = 0; s < sample_count; s++) { + for (ch_from = 0; ch_from < vgmstream->channels; ch_from++) { + if (ch_from > 32) + continue; + + ch_to = vgmstream->channel_mappings[ch_from]; + if (ch_to < 1 || ch_to > 32 || ch_to > vgmstream->channels-1 || ch_from == ch_to) + continue; + + temp = buffer[s*vgmstream->channels + ch_from]; + buffer[s*vgmstream->channels + ch_from] = buffer[s*vgmstream->channels + ch_to]; + buffer[s*vgmstream->channels + ch_to] = temp; + } + } + } + /* channel bitmask to silence non-set channels (up to 32) * can be used for 'crossfading subsongs' or layered channels, where a set of channels make a song section */ if (vgmstream->channel_mask) { diff --git a/src/vgmstream.h b/src/vgmstream.h index 945f45db..09ab619f 100644 --- a/src/vgmstream.h +++ b/src/vgmstream.h @@ -747,12 +747,14 @@ typedef struct { layout_t layout_type; /* type of layout for data */ meta_t meta_type; /* how we know the metadata */ - /* subsongs */ - int num_streams; /* for multi-stream formats (0=not set/one stream, 1=one stream) */ - int stream_index; /* selected stream (also 1-based) */ + /* subsongs and internal config */ + int num_streams; /* for multi-stream formats (0=not set/one stream, 1=one stream) */ + int stream_index; /* selected stream (also 1-based) */ char stream_name[STREAM_NAME_SIZE]; /* name of the current stream (info), if the file stores it and it's filled */ - size_t stream_size; /* info to properly calculate bitrate */ - uint32_t channel_mask; /* to silence crossfading subsongs/layers */ + size_t stream_size; /* info to properly calculate bitrate */ + 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]" */ /* looping */ int loop_flag; /* is this stream looped? */