FamiTracker import improvements (#2172)

* create instrument copies to accurately represent what FamiTracker does e.g. on VRC6 channels when using 2A03 instruments

* fix Hxy effect for S5B (AY), part 1

* fix Hxy effect for S5B (AY), part 2

* fix Hxy effect for S5B (AY), part 3
This commit is contained in:
LTVA1 2024-09-25 14:33:09 +03:00 committed by GitHub
parent 9011f80ae5
commit 95b7f1f3ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -268,6 +268,15 @@ int convertMacrosSID[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACR
int convert_vrc6_duties[4] = {1, 3, 7, 3};
int findEmptyFx(short* data)
{
for(int i = 0; i < 7; i++)
{
if(data[4 + i*2] == -1) return i;
}
return -1;
}
void copyMacro(DivInstrument* ins, DivInstrumentMacro* from, int macro_type, int setting) {
DivInstrumentMacro* to = NULL;
@ -445,6 +454,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
unsigned char vrc6_saw_chan = 0xff;
unsigned char n163_chans[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
unsigned char vrc7_chans[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
unsigned char s5b_chans[3] = {0xff, 0xff, 0xff};
unsigned char ay8930_chans[3] = {0xff, 0xff, 0xff};
// these two seem to go unused, but why?
unsigned char vrc6_chans[2] = {0xff, 0xff};
@ -734,6 +745,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
for (int ch = 0; ch < 3; ch++) {
map_channels[curr_chan] = map_ch;
s5b_chans[ch] = map_ch;
curr_chan++;
map_ch++;
}
@ -743,6 +755,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
for (int ch = 0; ch < 3; ch++) {
map_channels[curr_chan] = map_ch;
ay8930_chans[ch] = map_ch;
curr_chan++;
map_ch++;
}
@ -1939,6 +1952,13 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
}
}
for (int v = 0; v < 3; v++) {
if (map_channels[ch] == s5b_chans[v] || map_channels[ch] == ay8930_chans[v]) {
if (pat->data[row][4 + (j * 2)] == 0x22 && (pat->data[row][5 + (j * 2)] & 0xf0) != 0) {
pat->data[row][4 + (7 * 2)] = -666; //marker
}
}
}
} else {
pat->data[row][4 + (j * 2)] = -1;
pat->data[row][5 + (j * 2)] = -1;
@ -2428,13 +2448,104 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
}
for (int ii = 0; ii < total_chans; ii++) {
for (size_t j = 0; j < ds.subsong.size(); j++) {
for (int k = 0; k < DIV_MAX_PATTERNS; k++) {
if (ds.subsong[j]->pat[ii].data[k] == NULL)
continue;
for (int l = 0; l < ds.subsong[j]->patLen; l++) {
if (ds.subsong[j]->pat[ii].data[k]->data[l][4 + 7*2] == -666) {
bool converted = false;
for(int hh = 0; hh < 7; hh++) {
if(ds.subsong[j]->pat[ii].data[k]->data[l][4 + hh*2] == 0x22 && !converted) {
int slot = findEmptyFx(ds.subsong[j]->pat[ii].data[k]->data[l]);
if(slot != -1) {
//Hxy - Envelope automatic pitch
//Sets envelope period to the note period shifted by x and envelope type y.
//Approximate envelope frequency is note frequency * (2^|x - 8|) / 32.
int ftAutoEnv = (ds.subsong[j]->pat[ii].data[k]->data[l][5 + hh*2] >> 4) & 15;
int autoEnvDen = 16; // ???? with 32 it's an octave lower...
int autoEnvNum = (1 << (abs(ftAutoEnv - 8)));
while(autoEnvNum >= 2 && autoEnvDen >= 2)
{
autoEnvDen /= 2;
autoEnvNum /= 2;
}
if(autoEnvDen < 16 && autoEnvNum < 16)
{
ds.subsong[j]->pat[ii].data[k]->data[l][4 + slot*2] = 0x29;
ds.subsong[j]->pat[ii].data[k]->data[l][5 + slot*2] = (autoEnvNum << 4) | autoEnvDen;
}
ds.subsong[j]->pat[ii].data[k]->data[l][5 + hh*2] = (ds.subsong[j]->pat[ii].data[k]->data[l][5 + hh*2] & 0xf) << 4;
converted = true;
}
}
}
ds.subsong[j]->pat[ii].data[k]->data[l][4 + (7 * 2)] = -1; //delete marker
}
}
}
}
}
for (size_t j = 0; j < ds.subsong.size(); j++) { //open hidden effect columns
DivSubSong* s = ds.subsong[j];
for(int c = 0; c < total_chans; c++)
{
int num_fx = 1;
for(int p = 0; p < s->ordersLen; p++)
{
for(int r = 0; r < s->patLen; r++)
{
DivPattern* pat = s->pat[c].getPattern(s->orders.ord[c][p], true);
short* s_row_data = pat->data[r];
for(int eff = 0; eff < DIV_MAX_EFFECTS - 1; eff++)
{
if(s_row_data[4 + 2 * eff] != -1 && eff + 1 > num_fx)
{
num_fx = eff + 1;
}
}
}
}
s->pat[c].effectCols = num_fx;
}
}
// famitracker is not fucking strict with what instrument types can be used on any channel. This leads to e.g. 2A03 instuments being used on VRC6 channels.
// Furnace is way more strict, so NES instrument in VRC6 channel just does not play. To fix this, we are creating copies of instruments, changing their type to please Furnace system.
// I kinda did the same in klystrack import, tbh.
// Furnace is way more strict, so NES instrument in VRC6 channel just does not play properly. To fix this, we are creating copies of instruments, changing their type to please Furnace system.
// I kinda did the same in klystrack import, tbh. - LTVA
// actually, this is wrong. Furnace is very lax regarding instrument usage. you can put an OPM instrument in OPL channel and yeah, it'll sound weird but it'll work.
// actually, this is wrong. Furnace is very lax regarding instrument usage. you can put an OPM instrument in OPL channel and yeah, it'll sound weird but it'll work. - tildearrow
// I don't trust you
// where did you get that assumption from? did you make it up?
// first you go "I don't trust you" on me and then you drop this. couldn't
// you at least look around a bit?!
// since when do 2A03 and VRC6 duties match? who said that they do?
// Furnace wasn't designed to automatically convert duties of wrong instrument types, damn it!
// on top of that, you didn't have to drop this. AT ALL.
// instrument conversion is simpler than eating with a fork. you just copy the
// instruments and DON'T FORGET TO CONVERT DUTY MACROS (!!), than adapt VRC6 sawtooth volume and that's it.
// really? were these simplifications in import necessary? it isn't a new compat flag, after all.
// oh man....
// P.S. Duties conversion is based on what I really hear in FamiTracker when using wrong instrument type (and what I see on Audacity "oscilloscope") - LTVA
/*
int ins_vrc6_conv[256][2];
int ins_vrc6_saw_conv[256][2];
int ins_nes_conv[256][2]; // vrc6 (or whatever) -> nes
@ -2469,7 +2580,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
DivInstrument* insnew = new DivInstrument;
ds.ins.push_back(insnew);
copyInstrument(ds.ins[ds.ins.size() - 1], ins);
*ds.ins[ds.ins.size() - 1] = *ins;
ds.ins[ds.ins.size() - 1]->name += " [VRC6 copy]";
ds.ins[ds.ins.size() - 1]->amiga.useSample = false;
@ -2477,11 +2588,11 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
ds.ins[ds.ins.size() - 1]->type = DIV_INS_VRC6;
if (ins->std.get_macro(DIV_MACRO_DUTY, false)->len > 0) {
for (int mm = 0; mm < ins->std.get_macro(DIV_MACRO_DUTY, false)->len; mm++) {
if (ds.ins[ds.ins.size() - 1]->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] < 4) {
int vall = ins->std.get_macro(DIV_MACRO_DUTY, false)->val[mm];
ds.ins[ds.ins.size() - 1]->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] = convert_vrc6_duties[vall];
if (ins->std.dutyMacro.len > 0) {
for (int mm = 0; mm < ins->std.dutyMacro.len; mm++) {
if (ds.ins[ds.ins.size() - 1]->std.dutyMacro.val[mm] < 4) {
int vall = ins->std.dutyMacro.val[mm];
ds.ins[ds.ins.size() - 1]->std.dutyMacro.val[mm] = convert_vrc6_duties[vall];
}
}
}
@ -2521,7 +2632,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
DivInstrument* insnew = new DivInstrument;
ds.ins.push_back(insnew);
copyInstrument(ds.ins[ds.ins.size() - 1], ins);
*ds.ins[ds.ins.size() - 1] = *ins;
ds.ins[ds.ins.size() - 1]->name += " [VRC6 saw copy]";
ds.ins[ds.ins.size() - 1]->amiga.useSample = false;
@ -2529,11 +2640,11 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
ds.ins[ds.ins.size() - 1]->type = DIV_INS_VRC6_SAW;
if (ins->std.get_macro(DIV_MACRO_VOL, false)->len > 0) {
for (int mm = 0; mm < ins->std.get_macro(DIV_MACRO_VOL, false)->len; mm++) {
if (ds.ins[ds.ins.size() - 1]->std.get_macro(DIV_MACRO_VOL, false)->val[mm] < 16) {
int vall = ins->std.get_macro(DIV_MACRO_VOL, false)->val[mm];
ds.ins[ds.ins.size() - 1]->std.get_macro(DIV_MACRO_VOL, false)->val[mm] = vall * 42 / 15;
if (ins->std.volMacro.len > 0) {
for (int mm = 0; mm < ins->std.volMacro.len; mm++) {
if (ds.ins[ds.ins.size() - 1]->std.volMacro.val[mm] < 16) {
int vall = ins->std.volMacro.val[mm];
ds.ins[ds.ins.size() - 1]->std.volMacro.val[mm] = vall * 42 / 15;
}
}
}
@ -2573,32 +2684,32 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
DivInstrument* insnew = new DivInstrument;
ds.ins.push_back(insnew);
copyInstrument(ds.ins[ds.ins.size() - 1], ins);
*ds.ins[ds.ins.size() - 1] = *ins;
ds.ins[ds.ins.size() - 1]->name += " [NES copy]";
ds.ins[ds.ins.size() - 1]->type = DIV_INS_NES;
if (ins->type == DIV_INS_VRC6) {
if (insnew->std.get_macro(DIV_MACRO_DUTY, false)->len > 0) // convert duties for NES
if (insnew->std.dutyMacro.len > 0) // convert duties for NES
{
for (int mm = 0; mm < insnew->std.get_macro(DIV_MACRO_DUTY, false)->len; mm++) {
switch (insnew->std.get_macro(DIV_MACRO_DUTY, false)->val[mm]) {
for (int mm = 0; mm < insnew->std.dutyMacro.len; mm++) {
switch (insnew->std.dutyMacro.val[mm]) {
case 0:
case 1: {
insnew->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] = 0;
insnew->std.dutyMacro.val[mm] = 0;
break;
}
case 2:
case 3:
case 4:
case 5: {
insnew->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] = 1;
insnew->std.dutyMacro.val[mm] = 1;
break;
}
case 6:
case 7: {
insnew->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] = 2;
insnew->std.dutyMacro.val[mm] = 2;
break;
}
default:
@ -2652,7 +2763,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
}
}
*/
ds.delayBehavior=0;