diff --git a/src/engine/engine.h b/src/engine/engine.h index 982361f23..4ddd45ee8 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -547,7 +547,7 @@ class DivEngine { bool loadFur(unsigned char* file, size_t len, int variantID=0); bool loadMod(unsigned char* file, size_t len); bool loadS3M(unsigned char* file, size_t len); - bool loadFTM(unsigned char* file, size_t len); + bool loadFTM(unsigned char* file, size_t len, bool dnft, bool dnftSig, bool eft); bool loadFC(unsigned char* file, size_t len); void loadDMP(SafeReader& reader, std::vector& ret, String& stripPath); diff --git a/src/engine/fileOps/fileOpsCommon.cpp b/src/engine/fileOps/fileOpsCommon.cpp index 6eadfef62..7610d6260 100644 --- a/src/engine/fileOps/fileOpsCommon.cpp +++ b/src/engine/fileOps/fileOpsCommon.cpp @@ -128,7 +128,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { if (memcmp(file,DIV_DMF_MAGIC,16)==0) { return loadDMF(file,len); } else if (memcmp(file,DIV_FTM_MAGIC,18)==0) { - return loadFTM(file,len); + return loadFTM(file,len,false,false,false); } else if (memcmp(file,DIV_FUR_MAGIC,16)==0) { return loadFur(file,len); } else if (memcmp(file,DIV_FUR_MAGIC_DS0,16)==0) { diff --git a/src/engine/fileOps/ftm.cpp b/src/engine/fileOps/ftm.cpp index 152c7c6d3..729e2583c 100644 --- a/src/engine/fileOps/ftm.cpp +++ b/src/engine/fileOps/ftm.cpp @@ -17,14 +17,174 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +// most of this code written by LTVA +// ported to Furnace by tildearrow + +// TODO: +// - audit for CVEs +// - Namco 163 waves +// - fix virtual tempo +// - format code? + #include "fileOpsCommon.h" #define CHECK_BLOCK_VERSION(x) \ if (blockVersion>x) { \ - logW("incompatible block version %d for %s!",blockVersion,blockName); \ + logW("incompatible block version %d for %s!", blockVersion, blockName); \ } +enum FTEffects { + FT_EF_NONE = 0, + FT_EF_SPEED, // Speed + FT_EF_JUMP, // Jump + FT_EF_SKIP, // Skip + FT_EF_HALT, // Halt + FT_EF_VOLUME, // Volume + FT_EF_PORTAMENTO, // Porta on + FT_EF_PORTAOFF, // Porta off unused + FT_EF_SWEEPUP, // Sweep up + FT_EF_SWEEPDOWN, // Sweep down + FT_EF_ARPEGGIO, // Arpeggio + FT_EF_VIBRATO, // Vibrato + FT_EF_TREMOLO, // Tremolo + FT_EF_PITCH, // Pitch + FT_EF_DELAY, // Note delay + FT_EF_DAC, // DAC setting + FT_EF_PORTA_UP, // Portamento up + FT_EF_PORTA_DOWN, // Portamento down + FT_EF_DUTY_CYCLE, // Duty cycle + FT_EF_SAMPLE_OFFSET, // Sample offset + FT_EF_SLIDE_UP, // Slide up + FT_EF_SLIDE_DOWN, // Slide down + FT_EF_VOLUME_SLIDE, // Volume slide + FT_EF_NOTE_CUT, // Note cut + FT_EF_RETRIGGER, // DPCM retrigger + FT_EF_DELAYED_VOLUME, // // // Delayed channel volume + FT_EF_FDS_MOD_DEPTH, // FDS modulation depth + FT_EF_FDS_MOD_SPEED_HI, // FDS modulation speed hi + FT_EF_FDS_MOD_SPEED_LO, // FDS modulation speed lo + FT_EF_DPCM_PITCH, // DPCM Pitch + FT_EF_SUNSOFT_ENV_TYPE, // Sunsoft envelope type + FT_EF_SUNSOFT_ENV_HI, // Sunsoft envelope high + FT_EF_SUNSOFT_ENV_LO, // Sunsoft envelope low + FT_EF_SUNSOFT_NOISE, // // // 050B Sunsoft noise period + FT_EF_VRC7_PORT, // // // 050B VRC7 custom patch port + FT_EF_VRC7_WRITE, // // // 050B VRC7 custom patch write + FT_EF_NOTE_RELEASE, // // // Delayed release + FT_EF_GROOVE, // // // Groove + FT_EF_TRANSPOSE, // // // Delayed transpose + FT_EF_N163_WAVE_BUFFER, // // // N163 wave buffer + FT_EF_FDS_VOLUME, // // // FDS volume envelope + FT_EF_FDS_MOD_BIAS, // // // FDS auto-FM bias + FT_EF_PHASE_RESET, // Reset waveform phase without retriggering note (VRC6-only so far) + FT_EF_HARMONIC, // Multiply the note pitch by an integer + FT_EF_TARGET_VOLUME_SLIDE, // // !! Target volume slide + + FT_EF_COUNT +}; + const int ftEffectMap[]={ + -1, // none + 0x0f, + 0x0b, + 0x0d, + 0xff, + -1, // volume? not supported in Furnace yet + 0x03, + 0x03, // unused? + 0x13, 0x14, 0x00, 0x04, 0x07, 0xe5, 0xed, 0x11, + 0x01, // porta up + 0x02, // porta down + 0x12, + 0x90, // sample offset - not supported yet + 0xe1, // Slide up + 0xe2, // Slide down + 0x0a, 0xec, 0x0c, + -1, // delayed volume - not supported yet + 0x11, // FDS modulation depth + 0x12, // FDS modulation speed hi + 0x13, // FDS modulation speed lo + 0x20, // DPCM pitch + 0x22, // Sunsoft envelope type + 0x24, // Sunsoft envelope high + 0x23, // Sunsoft envelope low + 0x21, // 050B Sunsoft noise period + -1, // VRC7 "custom patch port" - not supported? + -1, // VRC7 "custom patch write" + 0xe7, // delayed release - not supported yet + 0x09, // select groove + 0xe6, // delayed note transpose + 0x11, // Namco 163 wave RAM offset + -1, // FDS vol env - not supported + -1, // FDS auto FM - not supported yet + -1, // phase reset - not supported + -1, // harmonic - not supported + -1, // target volume slide - not supported +}; + +enum EFTEffects { + EFT_EF_NONE = 0, + EFT_EF_SPEED, // Speed + EFT_EF_JUMP, // Jump + EFT_EF_SKIP, // Skip + EFT_EF_HALT, // Halt + EFT_EF_VOLUME, // Volume + EFT_EF_PORTAMENTO, // Porta on + EFT_EF_PORTAOFF, // Porta off unused + EFT_EF_SWEEPUP, // Sweep up + EFT_EF_SWEEPDOWN, // Sweep down + EFT_EF_ARPEGGIO, // Arpeggio + EFT_EF_VIBRATO, // Vibrato + EFT_EF_TREMOLO, // Tremolo + EFT_EF_PITCH, // Pitch + EFT_EF_DELAY, // Note delay + EFT_EF_DAC, // DAC setting + EFT_EF_PORTA_UP, // Portamento up + EFT_EF_PORTA_DOWN, // Portamento down + EFT_EF_DUTY_CYCLE, // Duty cycle + EFT_EF_SAMPLE_OFFSET, // Sample offset + EFT_EF_SLIDE_UP, // Slide up + EFT_EF_SLIDE_DOWN, // Slide down + EFT_EF_VOLUME_SLIDE, // Volume slide + EFT_EF_NOTE_CUT, // Note cut + EFT_EF_RETRIGGER, // DPCM retrigger + EFT_EF_DELAYED_VOLUME, // // // Delayed channel volume + EFT_EF_FDS_MOD_DEPTH, // FDS modulation depth + EFT_EF_FDS_MOD_SPEED_HI, // FDS modulation speed hi + EFT_EF_FDS_MOD_SPEED_LO, // FDS modulation speed lo + EFT_EF_DPCM_PITCH, // DPCM Pitch + EFT_EF_SUNSOFT_ENV_TYPE, // Sunsoft envelope type + EFT_EF_SUNSOFT_ENV_HI, // Sunsoft envelope high + EFT_EF_SUNSOFT_ENV_LO, // Sunsoft envelope low + EFT_EF_SUNSOFT_NOISE, // // // 050B Sunsoft noise period + EFT_EF_AY8930_PULSE_WIDTH, // // // AY8930 pulse width + EFT_EF_AY8930_AND_MASK, // // // AY8930 noise AND mask + EFT_EF_AY8930_OR_MASK, // // // AY8930 noise OR mask + EFT_EF_AY8930_VOL, // // // AY8930 extra volume bit + EFT_EF_VRC7_PORT, // // // 050B VRC7 custom patch port + EFT_EF_VRC7_WRITE, // // // 050B VRC7 custom patch write + EFT_EF_NOTE_RELEASE, // // // Delayed release + EFT_EF_GROOVE, // // // Groove + EFT_EF_TRANSPOSE, // // // Delayed transpose + EFT_EF_N163_WAVE_BUFFER, // // // N163 wave buffer + EFT_EF_FDS_VOLUME, // // // FDS volume envelope + EFT_EF_FDS_MOD_BIAS, // // // FDS auto-FM bias + EFT_EF_PHASE_RESET, // Reset waveform phase without retriggering note (VRC6-only so far) + EFT_EF_HARMONIC, // Multiply the note pitch by an integer + EFT_EF_PWM, // // // Pulse width modulation effect + EFT_EF_VOLUME_OFFSET, // // // Relative volume change + EFT_EF_SAA_NOISE_MODE, // // // SAA1099 noise mode + EFT_EF_SID_FILTER_RESONANCE, // // // SID filter resonance + EFT_EF_SID_FILTER_CUTOFF_HI, // // // SID filter cutoff hi + EFT_EF_SID_FILTER_CUTOFF_LO, // // // SID filter cutoff lo + EFT_EF_SID_FILTER_MODE, // // // SID filter mode + EFT_EF_SID_ENVELOPE, // // // SID envelope parameters + EFT_EF_SID_RING, // // // SID ringmod + + EFT_EF_COUNT +}; + +const int eftEffectMap[] = { -1, // none 0x0f, 0x0b, @@ -45,288 +205,704 @@ const int ftEffectMap[]={ 0x02, // porta down 0x12, 0x90, // sample offset - not supported yet - 0xe1, - 0xe2, + 0xe1, // Slide up + 0xe2, // Slide down 0x0a, 0xec, 0x0c, - -1, // delayed volume - not supported yet - 0x11, // FDS - 0x12, - 0x13, - 0x20, // DPCM pitch - 0x22, // 5B - 0x24, - 0x23, - 0x21, - -1, // VRC7 "custom patch port" - not supported? - -1, // VRC7 "custom patch write" - -1, // release - not supported yet - 0x09, // select groove - -1, // transpose - not supported - 0x10, // Namco 163 - -1, // FDS vol env - not supported - -1, // FDS auto FM - not supported yet - -1, // phase reset - not supported - -1, // harmonic - not supported + -1, // delayed volume - not supported yet + 0x11, // FDS modulation depth + 0x12, // FDS modulation speed hi + 0x13, // FDS modulation speed lo + 0x20, // DPCM pitch + 0x22, // Sunsoft envelope type + 0x24, // Sunsoft envelope high + 0x23, // Sunsoft envelope low + 0x21, // 050B Sunsoft noise period + 0x12, // // // AY8930 pulse width + 0x27, // // // AY8930 noise AND mask + 0x28, // // // AY8930 noise OR mask + 0x100, // // // AY8930 extra volume bit + -1, // VRC7 "custom patch port" - not supported? + -1, // VRC7 "custom patch write" + 0xe7, // delayed release - not supported yet + 0x09, // select groove + 0xe6, // delayed note transpose + 0x11, // Namco 163 wave RAM offset + -1, // FDS vol env - not supported + -1, // FDS auto FM - not supported yet + -1, // phase reset - not supported + -1, // harmonic - not supported + -1, // // // Pulse width modulation effect + -1, // // // Relative volume change + -1, // // // SAA1099 noise mode + 0x13, // // // SID filter resonance + 0x40, // // // SID filter cutoff hi + 0x40, // // // SID filter cutoff lo + 0x14, // // // SID filter mode + -1, // // // SID envelope parameters + -1, // // // SID ringmod }; -constexpr int ftEffectMapSize=sizeof(ftEffectMap)/sizeof(int); +const int eff_conversion_050[][2] = { + {FT_EF_SUNSOFT_NOISE, FT_EF_NOTE_RELEASE}, + {FT_EF_VRC7_PORT, FT_EF_GROOVE}, + {FT_EF_VRC7_WRITE, FT_EF_TRANSPOSE}, + {FT_EF_NOTE_RELEASE, FT_EF_N163_WAVE_BUFFER}, + {FT_EF_GROOVE, FT_EF_FDS_VOLUME}, + {FT_EF_TRANSPOSE, FT_EF_FDS_MOD_BIAS}, + {FT_EF_N163_WAVE_BUFFER, FT_EF_SUNSOFT_NOISE}, + {FT_EF_FDS_VOLUME, FT_EF_VRC7_PORT}, + {FT_EF_FDS_MOD_BIAS, FT_EF_VRC7_WRITE}, + {0xFF, 0xFF}, // end mark +}; -bool DivEngine::loadFTM(unsigned char* file, size_t len) { - SafeReader reader=SafeReader(file,len); - warnings=""; +constexpr int ftEffectMapSize = sizeof(ftEffectMap) / sizeof(int); + +int convertMacros2A03[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_DUTY}; +int convertMacrosVRC6[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_DUTY}; +int convertMacrosN163[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_WAVE}; +int convertMacros5B[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_DUTY}; + +int convertMacrosSID[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_WAVE}; + +int convert_vrc6_duties[4] = {1, 3, 7, 3}; + +void copyMacro(DivInstrument* ins, DivInstrumentMacro* from, int macro_type, int setting) { + DivInstrumentMacro* to = NULL; + + switch (ins->type) { + case DIV_INS_NES: { + if (convertMacros2A03[macro_type] == -1) + return; + to = ins->std.macroByType((DivMacroType)convertMacros2A03[macro_type]); + break; + } + case DIV_INS_VRC6: { + if (convertMacrosVRC6[macro_type] == -1) + return; + to = ins->std.macroByType((DivMacroType)convertMacros2A03[macro_type]); + break; + } + case DIV_INS_N163: { + if (convertMacrosVRC6[macro_type] == -1) + return; + to = ins->std.macroByType((DivMacroType)convertMacrosN163[macro_type]); + break; + } + case DIV_INS_AY: { + if (convertMacrosVRC6[macro_type] == -1) + return; + to = ins->std.macroByType((DivMacroType)convertMacros5B[macro_type]); + break; + } + case DIV_INS_C64: { + if (convertMacrosVRC6[macro_type] == -1) + return; + to = ins->std.macroByType((DivMacroType)convertMacrosSID[macro_type]); + break; + } + default: + break; + } + + if (to == NULL) + return; + + for (int i = 0; i < 256; i++) { + to->val[i] = from->val[i]; + + if ((DivMacroType)convertMacros2A03[macro_type] == DIV_MACRO_ARP) { + if (setting == 0) // absolute + { + if (to->val[i] > 0x60) { + int temp = to->val[i]; + to->val[i] = -1 * (0xff - temp + 1); // 2s complement integer my beloved + } + } + + if (setting == 1) // fixed + { + to->val[i] |= (1 << 30); // 30th bit in Furnace arp macro marks fixed mode + } + } + + if ((DivMacroType)convertMacros2A03[macro_type] == DIV_MACRO_PITCH) { + if (setting == 0 || setting == 1) // relative/absolute + { + int temp = to->val[i]; + + if (temp < 0x80) { + to->val[i] = -1 * temp; + } else { + to->val[i] = (0x100 - temp); + } + } + } + + if ((DivMacroType)convertMacrosN163[macro_type] == DIV_MACRO_WAVE && ins->type == DIV_INS_N163) { + to->val[i] |= (1 << 30); // referencing local wavetables! + } + + if ((DivMacroType)convertMacrosN163[macro_type] == DIV_MACRO_PITCH && (ins->type == DIV_INS_N163 || ins->type == DIV_INS_C64)) { + to->val[i] *= -1; // wtf is going on!!! + } + } + + to->len = from->len; + to->delay = from->delay; + to->lenMemory = from->lenMemory; + to->mode = from->mode; + to->rel = from->rel; + to->speed = from->speed; + to->loop = from->loop; + to->open = from->open; + + if ((DivMacroType)convertMacros2A03[macro_type] == DIV_MACRO_ARP) { + if (setting == 1) // fixed + { + if (to->loop == 255) // no loop + { + to->len++; + to->val[to->len - 1] = 0; // return to orig pitch (relative mode, 0 offset) + } + } + } + + if ((DivMacroType)convertMacros2A03[macro_type] == DIV_MACRO_PITCH) { + if (setting == 0) // relative + { + to->mode = 1; // setting relative mode + } + } + + if (ins->type == DIV_INS_AY && macro_type == 4) // S5B noise/mode macro combines noise freq and tone/env/noise settings, so we need to separate them into two macros + { + DivInstrumentMacro* wave = &ins->std.waveMacro; + to = &ins->std.dutyMacro; + + wave->len = to->len; + wave->delay = to->delay; + wave->lenMemory = to->lenMemory; + wave->mode = to->mode; + wave->rel = to->rel; + wave->speed = to->speed; + wave->loop = to->loop; + wave->open = to->open; + + for (int i = 0; i < to->len; i++) { + // ? ? ? ? + + logI("%02X", to->val[i]); + wave->val[i] = 0; + + int temp = 0; + + if (to->val[i] & 0b10000000) // noise + { + temp |= 2; + } + if (to->val[i] & 0b01000000) // tone + { + temp |= 1; + } + if (to->val[i] & 0b00100000) // envelope + { + temp |= 4; + } + + wave->val[i] = temp; + + // #define S5B_ENVL 0b10000000 + // #define S5B_TONE 0b01000000 + // #define S5B_NOIS 0b00100000 + + to->val[i] = to->val[i] & 31; + } + } +} + +bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_sig, bool eft) { + SafeReader reader = SafeReader(file, len); + warnings = ""; try { DivSong ds; String blockName; - unsigned char expansions=0; - unsigned int tchans=0; - unsigned int n163Chans=0; + unsigned int expansions = 0; + unsigned int tchans = 0; + unsigned int n163Chans = 0; bool hasSequence[256][8]; unsigned char sequenceIndex[256][8]; - unsigned int hilightA=4; - unsigned int hilightB=16; - double customHz=60; - - memset(hasSequence,0,256*8*sizeof(bool)); - memset(sequenceIndex,0,256*8); + unsigned char macro_types[256][8]; + std::vector> macros; + unsigned char map_channels[DIV_MAX_CHANS]; + unsigned int hilightA = 4; + unsigned int hilightB = 16; + double customHz = 60; - if (!reader.seek(18,SEEK_SET)) { + unsigned char fds_chan = 0xff; + 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}; + + // these two seem to go unused, but why? + unsigned char vrc6_chans[2] = {0xff, 0xff}; + unsigned char mmc5_chans[2] = {0xff, 0xff}; + + int total_chans = 0; + + memset(hasSequence, 0, 256 * 8 * sizeof(bool)); + memset(sequenceIndex, 0, 256 * 8); + memset(macro_types, 0, 256 * 8); + memset(map_channels, 0xfe, DIV_MAX_CHANS * sizeof(unsigned char)); + + for (int i = 0; i < 256; i++) { + std::vector mac; + + for (int j = 0; j < 8; j++) { + mac.push_back(DivInstrumentMacro(DIV_MACRO_VOL)); + } + + macros.push_back(mac); + } + + if (!reader.seek((dnft && dnft_sig) ? 21 : 18, SEEK_SET)) { logE("premature end of file!"); - lastError="incomplete file"; + lastError = "incomplete file"; delete[] file; return false; } - ds.version=(unsigned short)reader.readI(); - logI("module version %d (0x%.4x)",ds.version,ds.version); + ds.version = (unsigned short)reader.readI(); + logI("module version %d (0x%.4x)", ds.version, ds.version); - if (ds.version>0x0450) { - logE("incompatible version %x!",ds.version); - lastError="incompatible version"; + if ((ds.version > 0x0450 && !eft) || (eft && ds.version > 0x0460)) { + logE("incompatible version %x!", ds.version); + lastError = "incompatible version"; delete[] file; return false; } - for (DivSubSong* i: ds.subsong) { + for (DivSubSong* i : ds.subsong) { i->clearData(); delete i; } ds.subsong.clear(); - ds.linearPitch=0; + ds.linearPitch = 0; + + unsigned int pal = 0; while (true) { - blockName=reader.readString(3); - if (blockName=="END") { + blockName = reader.readString(3); + if (blockName == "END") { // end of module logD("end of data"); break; } // not the end - reader.seek(-3,SEEK_CUR); - blockName=reader.readString(16); - unsigned int blockVersion=(unsigned int)reader.readI(); - unsigned int blockSize=(unsigned int)reader.readI(); - size_t blockStart=reader.tell(); - - logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize); - if (blockName=="PARAMS") { + reader.seek(-3, SEEK_CUR); + blockName = reader.readString(16); + unsigned int blockVersion = (unsigned int)reader.readI(); + unsigned int blockSize = (unsigned int)reader.readI(); + size_t blockStart = reader.tell(); + + logD("reading block %s (version %d, %d bytes, position %x)", blockName, blockVersion, blockSize, reader.tell()); + if (blockName == "PARAMS") { // versions 7-9 don't change anything? CHECK_BLOCK_VERSION(9); - unsigned int oldSpeedTempo=0; - if (blockVersion<=1) { - oldSpeedTempo=reader.readI(); + unsigned int oldSpeedTempo = 0; + if (blockVersion <= 1) { + oldSpeedTempo = reader.readI(); } - if (blockVersion>=2) { - expansions=reader.readC(); + if (blockVersion >= 2) { + if (eft) { + if (blockVersion < 7) { + expansions = reader.readC(); + } else { + expansions = reader.readI(); + } + } else { + expansions = reader.readC(); + } } - tchans=reader.readI(); - unsigned int pal=reader.readI(); - if (blockVersion>=7) { - // advanced Hz control - int controlType=reader.readI(); - switch (controlType) { - case 1: - customHz=1000000.0/(double)reader.readI(); - break; - default: - reader.readI(); - break; + + tchans = reader.readI(); + + if (tchans == 5) { + expansions = 0; // This is strange. Sometimes expansion chip is set to 0xFF in files + } + + pal = reader.readI(); + if (!eft) { + if (blockVersion >= 7) { + // advanced Hz control + int controlType = reader.readI(); + switch (controlType) { + case 1: + customHz = 1000000.0 / (double)reader.readI(); + break; + default: + reader.readI(); + break; + } + } else { + customHz = reader.readI(); } } else { - customHz=reader.readI(); - } - unsigned int newVibrato=0; - bool sweepReset=false; - unsigned int speedSplitPoint=0; - if (blockVersion>=3) { - newVibrato=reader.readI(); - } - if (blockVersion>=9) { - sweepReset=reader.readI(); - } - if (blockVersion>=4 && blockVersion<7) { - hilightA=reader.readI(); - hilightB=reader.readI(); - } - if (expansions&8) if (blockVersion>=5) { // N163 channels - n163Chans=reader.readI(); - } - if (blockVersion>=6) { - speedSplitPoint=reader.readI(); + customHz = reader.readI(); } - if (blockVersion>=8) { - int fineTuneCents=reader.readC()*100; - fineTuneCents+=reader.readC(); - - ds.tuning=440.0*pow(2.0,(double)fineTuneCents/1200.0); + unsigned int newVibrato = 0; + bool sweepReset = false; + unsigned int speedSplitPoint = 0; + if (blockVersion >= 3) { + newVibrato = reader.readI(); + } + if (blockVersion >= 9) { + sweepReset = reader.readI(); + } + if (eft) { + if (blockVersion >= 4 && blockVersion <= 7) { + hilightA = reader.readI(); + hilightB = reader.readI(); + } + } else { + if (blockVersion >= 4 && blockVersion < 7) { + hilightA = reader.readI(); + hilightB = reader.readI(); + } } - logV("old speed/tempo: %d",oldSpeedTempo); - logV("expansions: %x",expansions); - logV("channels: %d",tchans); - logV("PAL: %d",pal); - logV("custom Hz: %f",customHz); - logV("new vibrato: %d",newVibrato); - logV("N163 channels: %d",n163Chans); - logV("highlight 1: %d",hilightA); - logV("highlight 2: %d",hilightB); - logV("split point: %d",speedSplitPoint); - logV("sweep reset: %d",sweepReset); + if ((expansions & 16) && blockVersion >= 5) { // N163 channels + n163Chans = reader.readI(); + } + if (blockVersion >= 6) { + speedSplitPoint = reader.readI(); + } + + if (blockVersion == 8) { + int fineTuneCents = reader.readC() * 100; + fineTuneCents += reader.readC(); + + ds.tuning = 440.0 * pow(2.0, (double)fineTuneCents / 1200.0); + + logV("detune: %d", fineTuneCents); + } + + logV("old speed/tempo: %d", oldSpeedTempo); + logV("expansions: %x", expansions); + logV("channels: %d", tchans); + logV("PAL: %d", pal); + logV("custom Hz: %f", customHz); + logV("new vibrato: %d", newVibrato); + logV("N163 channels: %d", n163Chans); + logV("highlight 1: %d", hilightA); + logV("highlight 2: %d", hilightB); + logV("split point: %d", speedSplitPoint); + logV("sweep reset: %d", sweepReset); + + //addWarning("FamiTracker import is experimental."); // initialize channels - int systemID=0; - ds.system[systemID++]=DIV_SYSTEM_NES; - if (expansions&1) { - ds.system[systemID++]=DIV_SYSTEM_VRC6; - } - if (expansions&2) { - ds.system[systemID++]=DIV_SYSTEM_VRC7; - } - if (expansions&4) { - ds.system[systemID++]=DIV_SYSTEM_FDS; - } - if (expansions&8) { - ds.system[systemID++]=DIV_SYSTEM_MMC5; - } - if (expansions&16) { - ds.system[systemID]=DIV_SYSTEM_N163; - ds.systemFlags[systemID++].set("channels",(int)n163Chans); - } - if (expansions&32) { - ds.system[systemID]=DIV_SYSTEM_AY8910; - ds.systemFlags[systemID++].set("chipType",2); // Sunsoft 5B - } - ds.systemLen=systemID; + int systemID = 0; - unsigned int calcChans=0; - for (int i=0; iDIV_MAX_CHANS) { - tchans=DIV_MAX_CHANS; - logW("too many channels!"); - } - } else if (blockName=="INFO") { - CHECK_BLOCK_VERSION(1); - ds.name=reader.readString(32); - ds.author=reader.readString(32); - ds.category=reader.readString(32); - ds.systemName="NES"; - } else if (blockName=="HEADER") { - CHECK_BLOCK_VERSION(4); - unsigned char totalSongs=reader.readC(); - logV("%d songs:",totalSongs+1); - ds.subsong.reserve(totalSongs); - for (int i=0; i<=totalSongs; i++) { - String subSongName=reader.readString(); - ds.subsong.push_back(new DivSubSong); - ds.subsong[i]->name=subSongName; - ds.subsong[i]->hilightA=hilightA; - ds.subsong[i]->hilightB=hilightB; - if (customHz!=0) { - ds.subsong[i]->hz=customHz; + + if (expansions & 1) { + ds.system[systemID++] = DIV_SYSTEM_VRC6; + + for (int ch = 0; ch < 3; ch++) { + map_channels[curr_chan] = map_ch; + + if (ch < 2) { + vrc6_chans[ch] = map_ch; + logV("%d",vrc6_chans[ch]); + } + + curr_chan++; + map_ch++; } - logV("- %s",subSongName); + + vrc6_saw_chan = map_ch - 1; } - for (unsigned int i=0; ipat[i].effectCols=effectCols+1; - logV("- song %d has %d effect columns",j,effectCols); + if (expansions & 8) { + ds.system[systemID++] = DIV_SYSTEM_MMC5; + + for (int ch = 0; ch < (eft ? 3 : 2); ch++) { + map_channels[curr_chan] = map_ch; + + if (ch < 2) { + mmc5_chans[ch] = map_ch; + logV("%d",mmc5_chans[ch]); + } + + curr_chan++; + map_ch++; + } + + if (!eft) { + map_channels[curr_chan] = map_ch; // do not populate and skip MMC5 PCM channel! + map_ch++; } } + if (expansions & 16) { + ds.system[systemID] = DIV_SYSTEM_N163; + ds.systemFlags[systemID].set("channels", (int)n163Chans - 1); + systemID++; - if (blockVersion>=4) { - for (int i=0; i<=totalSongs; i++) { - ds.subsong[i]->hilightA=(unsigned char)reader.readC(); - ds.subsong[i]->hilightB=(unsigned char)reader.readC(); + for (int ch = 0; ch < (int)n163Chans; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + n163_chans[ch] = map_ch; + map_ch++; + } + + for (int ch = 0; ch < (8 - (int)n163Chans); ch++) { + map_channels[curr_chan] = map_ch; // do not populate and skip the missing N163 channels! + map_ch++; } } - } else if (blockName=="INSTRUMENTS") { - CHECK_BLOCK_VERSION(6); + if (expansions & 4) { + ds.system[systemID++] = DIV_SYSTEM_FDS; - reader.seek(blockSize,SEEK_CUR); + map_channels[curr_chan] = map_ch; + fds_chan = map_ch; + curr_chan++; + map_ch++; + } + if (expansions & 2) { + ds.system[systemID++] = DIV_SYSTEM_VRC7; - /* - ds.insLen=reader.readI(); - if (ds.insLen<0 || ds.insLen>256) { - logE("too many instruments/out of range!"); - lastError="too many instruments/out of range"; - delete[] file; - return false; + for (int ch = 0; ch < 6; ch++) { + map_channels[curr_chan] = map_ch; + vrc7_chans[ch] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 32) { + ds.system[systemID] = DIV_SYSTEM_AY8910; + ds.systemFlags[systemID++].set("chipType", 2); // Sunsoft 5B + + for (int ch = 0; ch < 3; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 64) { + ds.system[systemID++] = DIV_SYSTEM_AY8930; + + for (int ch = 0; ch < 3; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 128) { + ds.system[systemID++] = DIV_SYSTEM_SAA1099; + + for (int ch = 0; ch < 6; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 256) { + // TODO: change when 5E01 is added + ds.system[systemID++] = DIV_SYSTEM_NES; + + for (int ch = 0; ch < 5; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 512) { + ds.system[systemID++] = DIV_SYSTEM_C64_6581; + + for (int ch = 0; ch < 3; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 1024) { + ds.system[systemID++] = DIV_SYSTEM_C64_8580; + + for (int ch = 0; ch < 3; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + if (expansions & 2048) { + ds.system[systemID++] = DIV_SYSTEM_POKEY; + + for (int ch = 0; ch < 4; ch++) { + map_channels[curr_chan] = map_ch; + curr_chan++; + map_ch++; + } + } + ds.systemLen = systemID; + + for (int i = 0; i < curr_chan; i++) { + logV("map ch: fami ch %d mapped to furnace ch %d", i, map_channels[i]); } - for (int i=0; i=ds.ins.size()) { - logE("instrument index %d is out of range!",insIndex); - lastError="instrument index out of range"; + calcChans += getChannelCount(ds.system[i]); + total_chans += getChannelCount(ds.system[i]); + + if (ds.system[i] == DIV_SYSTEM_N163) { + calcChans -= getChannelCount(ds.system[i]); + calcChans += (int)n163Chans; + } + } + if (calcChans != tchans) { + if (!eft || (eft && (expansions & 8) == 0)) // ignore since I have no idea how to tell apart E-FT versions which do or do not have PCM chan. Yes, this may lead to all the righer channels to be shifted but at least you still get note data! + { + logE("channel counts do not match! %d != %d", tchans, calcChans); + lastError = "channel counts do not match"; delete[] file; return false; } + } + if (tchans > DIV_MAX_CHANS) { + tchans = DIV_MAX_CHANS; + logW("too many channels!"); + } + if (blockVersion == 9 && blockSize - (reader.tell() - blockStart) == 2) // weird + { + reader.seek(2, SEEK_CUR); + } + if (eft) { + // reader.seek(8,SEEK_CUR); + } - DivInstrument* ins=ds.ins[insIndex]; - unsigned char insType=reader.readC(); + } else if (blockName == "INFO") { + CHECK_BLOCK_VERSION(1); + ds.name = reader.readString(32); + ds.author = reader.readString(32); + ds.copyright = reader.readString(32); + ds.systemName = "NES"; + } else if (blockName == "HEADER") { + CHECK_BLOCK_VERSION(4); + unsigned char totalSongs = reader.readC(); + logV("%d songs:", totalSongs + 1); + ds.subsong.reserve(totalSongs); + for (int i = 0; i <= totalSongs; i++) { + String subSongName = reader.readString(); + ds.subsong.push_back(new DivSubSong); + ds.subsong[i]->name = subSongName; + ds.subsong[i]->hilightA = hilightA; + ds.subsong[i]->hilightB = hilightB; + if (customHz != 0) { + ds.subsong[i]->hz = customHz; + + ds.subsong[i]->virtualTempoN = (short)(150.0 / (float)customHz * (pal ? (50.0) : (60.0))); + } + logV("- %s", subSongName); + } + for (unsigned int i = 0; i < tchans; i++) { + // TODO: obey channel ID + unsigned char chID = reader.readC(); + logV("for channel ID %d", chID); + for (int j = 0; j <= totalSongs; j++) { + unsigned char effectCols = reader.readC(); + + if (map_channels[i] == 0xfe) { + ds.subsong[j]->pat[i].effectCols = 1; + logV("- song %d has %d effect columns", j, effectCols); + } else { + ds.subsong[j]->pat[map_channels[i]].effectCols = effectCols + 1; + logV("- song %d has %d effect columns", j, effectCols); + } + } + } + + if (blockVersion >= 4) { + for (int i = 0; i <= totalSongs; i++) { + ds.subsong[i]->hilightA = (unsigned char)reader.readC(); + ds.subsong[i]->hilightB = (unsigned char)reader.readC(); + } + } + } else if (blockName == "INSTRUMENTS") { + CHECK_BLOCK_VERSION(9); + + // reader.seek(blockSize,SEEK_CUR); + + ds.insLen = reader.readI(); + if (ds.insLen < 0 || ds.insLen > 256) { + logE("too many instruments/out of range!"); + lastError = "too many instruments/out of range"; + delete[] file; + return false; + } + + for (int i = 0; i < 128; i++) { + DivInstrument* ins = new DivInstrument; + ds.ins.push_back(ins); + ds.ins[i]->type = DIV_INS_FM; + } + + logV("instruments:"); + for (int i = 0; i < ds.insLen; i++) { + unsigned int insIndex = reader.readI(); + if (insIndex >= ds.ins.size()) { + // logE("instrument index %d is out of range!",insIndex); + // lastError="instrument index out of range"; + // delete[] file; + // return false; + } + + DivInstrument* ins = ds.ins[insIndex]; + unsigned char insType = reader.readC(); switch (insType) { case 1: - ins->type=DIV_INS_NES; + ins->type = DIV_INS_NES; break; - case 2: // TODO: tell VRC6 and VRC6 saw instruments apart - ins->type=DIV_INS_VRC6; + case 2: + ins->type = DIV_INS_VRC6; break; - case 3: - ins->type=DIV_INS_OPLL; + case 3: // VRC7 + ins->type = DIV_INS_OPLL; break; case 4: - ins->type=DIV_INS_FDS; + ins->type = DIV_INS_FDS; break; case 5: - ins->type=DIV_INS_N163; + ins->type = DIV_INS_N163; break; - case 6: // 5B? - ins->type=DIV_INS_AY; + case 6: // 5B + ins->type = DIV_INS_AY; + break; + case 7: // 6581 SID + ins->type = DIV_INS_C64; break; default: { - logE("%d: invalid instrument type %d",insIndex,insType); - lastError="invalid instrument type"; + logE("%d: invalid instrument type %d", insIndex, insType); + lastError = "invalid instrument type"; delete[] file; return false; } @@ -335,253 +911,1470 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { // instrument data switch (ins->type) { case DIV_INS_NES: { - unsigned int totalSeqs=reader.readI(); - if (totalSeqs>5) { - logE("%d: too many sequences!",insIndex); - lastError="too many sequences"; + unsigned int totalSeqs = reader.readI(); + if (totalSeqs > 5) { + logE("%d: too many sequences!", insIndex); + lastError = "too many sequences"; delete[] file; return false; } - for (unsigned int j=0; j=2)?96:72; - for (int j=0; jamiga.noteMap[j].map=(short)((unsigned char)reader.readC())-1; - ins->amiga.noteMap[j].freq=(unsigned char)reader.readC(); - if (blockVersion>=6) { - reader.readC(); // DMC value + int dpcmNotes = (blockVersion >= 2) ? 96 : 72; + + if (blockVersion >= 7) { + unsigned int notes = reader.readI(); + dpcmNotes = notes; + } + + for (int j = 0; j < dpcmNotes; j++) { + int note = j; + if (blockVersion >= 7) { + note = reader.readC(); } + ins->amiga.noteMap[note].map = (short)((unsigned char)reader.readC()) - 1; + unsigned char freq = reader.readC(); + ins->amiga.noteMap[note].dpcmFreq = (freq & 15); // 0-15 = 0-15 unlooped, 128-143 = 0-15 looped + ins->amiga.noteMap[note].freq = (freq & 0x80) ? 1 : 0; // loop + if (blockVersion >= 6) { + ins->amiga.noteMap[note].dpcmDelta = (unsigned char)reader.readC(); // DMC value + } + } + + ins->amiga.useSample = true; + ins->amiga.useNoteMap = true; + + bool empty_note_map = true; + + for (int j = 0; j < dpcmNotes; j++) { + if (ins->amiga.noteMap[j].map != -1) { + empty_note_map = false; + } + } + + if (empty_note_map) { + ins->amiga.useSample = false; + ins->amiga.useNoteMap = false; + ins->amiga.initSample = -1; } break; } case DIV_INS_VRC6: { - unsigned int totalSeqs=reader.readI(); - if (totalSeqs>4) { - logE("%d: too many sequences!",insIndex); - lastError="too many sequences"; + unsigned int totalSeqs = reader.readI(); + if (totalSeqs > 5) { + logE("%d: too many sequences!", insIndex); + lastError = "too many sequences"; delete[] file; return false; } - for (unsigned int j=0; jfm.opllPreset=(unsigned int)reader.readI(); - // TODO + ins->fm.opllPreset = (unsigned int)reader.readI(); + + unsigned char custom_patch[8] = {0}; + + for (int i = 0; i < 8; i++) { + custom_patch[i] = reader.readC(); + } + + for (int i = 0; i < 2; i++) { + ins->fm.op[i].am = custom_patch[i] >> 7; + ins->fm.op[i].vib = (custom_patch[i] >> 6) & 1; + ins->fm.op[i].ssgEnv = ((custom_patch[i] >> 5) & 1) << 3; + ins->fm.op[i].ksr = (custom_patch[i] >> 4) & 1; + + ins->fm.op[i].mult = custom_patch[i] & 15; + + ins->fm.op[i].ksl = custom_patch[i + 2] >> 6; + + ins->fm.op[i].ar = custom_patch[i + 4] >> 4; + ins->fm.op[i].dr = custom_patch[i + 4] & 15; + + ins->fm.op[i].sl = custom_patch[i + 6] >> 4; + ins->fm.op[i].rr = custom_patch[i + 6] & 15; + } + + ins->fm.fms = (custom_patch[3] >> 4) & 1; + ins->fm.ams = (custom_patch[3] >> 3) & 1; + ins->fm.fb = custom_patch[3] & 7; + + ins->fm.op[0].tl = custom_patch[2] & 0x3f; + ins->fm.op[1].tl = 0; break; } case DIV_INS_FDS: { - DivWavetable* wave=new DivWavetable; - wave->len=64; - wave->max=64; - for (int j=0; j<64; j++) { - wave->data[j]=reader.readC(); + DivWavetable* wave = new DivWavetable; + wave->len = 64; + wave->max = 63; + for (int j = 0; j < 64; j++) { + wave->data[j] = reader.readC(); } - ins->std.waveMacro.len=1; - ins->std.waveMacro.val[0]=ds.wave.size(); - for (int j=0; j<32; j++) { - ins->fds.modTable[j]=reader.readC()-3; + ins->std.waveMacro.len = 1; + ins->std.waveMacro.val[0] = ds.wave.size(); + for (int j = 0; j < 32; j++) { + ins->fds.modTable[j] = reader.readC() - 3; } - ins->fds.modSpeed=reader.readI(); - ins->fds.modDepth=reader.readI(); + ins->fds.modSpeed = reader.readI(); + ins->fds.modDepth = reader.readI(); reader.readI(); // this is delay. currently ignored. TODO. ds.wave.push_back(wave); + ds.waveLen++; - ins->std.volMacro.len=reader.readC(); - ins->std.volMacro.loop=reader.readI(); - ins->std.volMacro.rel=reader.readI(); - reader.readI(); // arp mode does not apply here - for (int j=0; jstd.volMacro.len; j++) { - ins->std.volMacro.val[j]=reader.readC(); - } + unsigned int a = reader.readI(); + unsigned int b = reader.readI(); - ins->std.arpMacro.len=reader.readC(); - ins->std.arpMacro.loop=reader.readI(); - ins->std.arpMacro.rel=reader.readI(); - // TODO: get rid - ins->std.arpMacro.mode=reader.readI(); - for (int j=0; jstd.arpMacro.len; j++) { - ins->std.arpMacro.val[j]=reader.readC(); - } + reader.seek(-8, SEEK_CUR); - ins->std.pitchMacro.len=reader.readC(); - ins->std.pitchMacro.loop=reader.readI(); - ins->std.pitchMacro.rel=reader.readI(); - reader.readI(); // arp mode does not apply here - for (int j=0; jstd.pitchMacro.len; j++) { - ins->std.pitchMacro.val[j]=reader.readC(); + if (a < 256 && (b & 0xFF) != 0x00) { + // don't look at me like this. I don't know why this should be like this either! + } else { + ins->std.volMacro.len = reader.readC(); + ins->std.volMacro.loop = reader.readI(); + ins->std.volMacro.rel = reader.readI(); + reader.readI(); // arp mode does not apply here + for (int j = 0; j < ins->std.volMacro.len; j++) { + ins->std.volMacro.val[j] = reader.readC(); + + if (blockVersion <= 3) { + ins->std.volMacro.val[j] *= 2; + } + } + + ins->std.arpMacro.len = reader.readC(); + ins->std.arpMacro.loop = reader.readI(); + ins->std.arpMacro.rel = reader.readI(); + unsigned int mode = reader.readI(); + for (int j = 0; j < ins->std.arpMacro.len; j++) { + ins->std.arpMacro.val[j] = reader.readC(); + + if (mode == 1) // fixed arp + { + ins->std.arpMacro.val[j] |= (1 << 30); + } + } + + if (blockVersion >= 3) { + ins->std.pitchMacro.len = reader.readC(); + ins->std.pitchMacro.loop = reader.readI(); + ins->std.pitchMacro.rel = reader.readI(); + reader.readI(); // arp mode does not apply here + for (int j = 0; j < ins->std.pitchMacro.len; j++) { + ins->std.pitchMacro.val[j] = reader.readC(); + + int temp_val = ins->std.pitchMacro.val[j]; + int temp = temp_val; + + if (temp_val < 0x80) { + ins->std.pitchMacro.val[j] = temp; + } else { + ins->std.pitchMacro.val[j] = -1 * (0x100 - temp); + } + } + + if (mode == 0) // relative + { + ins->std.pitchMacro.mode = 1; // setting relative mode + } + } } break; } - case DIV_INS_N163: { - // TODO! + case DIV_INS_N163: { // TODO: add local wavetables and finish this! + unsigned int totalSeqs = reader.readI(); + if (totalSeqs > 5) { + logE("%d: too many sequences!", insIndex); + lastError = "too many sequences"; + delete[] file; + return false; + } + + for (unsigned int j = 0; j < totalSeqs; j++) { + hasSequence[insIndex][j] = reader.readC(); + sequenceIndex[insIndex][j] = reader.readC(); + } + + unsigned int wave_size = reader.readI(); + unsigned int wave_pos = reader.readI(); + ins->n163.waveLen = wave_size; + ins->n163.wavePos = wave_pos; + + if (blockVersion >= 8) { + unsigned int autopos = reader.readI(); + (void)autopos; + } + + unsigned int wave_count = reader.readI(); + + for (unsigned int ii = 0; ii < wave_count; ii++) { + DivWavetable* wave = new DivWavetable(); + wave->len = wave_size; + wave->max = 15; + + for (unsigned int jj = 0; jj < wave_size; jj++) { + unsigned char val = reader.readC(); + wave->data[jj] = val; + } + + // TODO: port to global waves + //ins->std.local_waves.push_back(wave); + delete wave; + } + + if (ins->std.waveMacro.len == 0) // empty wave macro + { + ins->std.waveMacro.len = 1; + ins->std.waveMacro.val[0] = 0 | (1 << 30); // force local wave number 0 + } + break; } - // TODO: 5B! + + case DIV_INS_AY: { + unsigned int totalSeqs = reader.readI(); + if (totalSeqs > 5) { + logE("%d: too many sequences!", insIndex); + lastError ="too many sequences"; + delete[] file; + return false; + } + + for (unsigned int j = 0; j < totalSeqs; j++) { + hasSequence[insIndex][j] = reader.readC(); + sequenceIndex[insIndex][j] = reader.readC(); + } + + break; + } + + case DIV_INS_C64: { + unsigned int instVersion = reader.readI(); + logV("C64 inst version %d", instVersion); + + int seek_amount = 94 - 4; + + if (instVersion <= 255) { + unsigned char ad = reader.readC(); + unsigned char sr = reader.readC(); + ins->c64.a = ad >> 4; + ins->c64.d = ad & 15; + ins->c64.s = sr >> 4; + ins->c64.r = sr & 15; + + ins->c64.pulseOn = true; + ins->c64.sawOn = false; + + seek_amount -= 2; + + if (instVersion >= 2) { + unsigned int pwm_start = reader.readI(); + unsigned int pwm_end = reader.readI(); + + unsigned char pwm_speed = reader.readC(); // for LFO mode * 40 / 69 * (diff / (2048 - 311)) (and divide by two for triangle mode) + unsigned char pwm_mode = reader.readC(); // for ADSR mode * 11 / 69 + + if (pwm_mode != 0) { + ins->c64.dutyIsAbs = true; + + logV("pwm mode %d", pwm_mode); + + // modes: 1 = saw lfo, 2 = tri lfo, 3 = oneshot ADSR, 4 = set val? + + ins->std.dutyMacro.len = 18; + + if (pwm_mode != 4) { + ins->std.dutyMacro.val[0] = pwm_start; + ins->std.dutyMacro.val[1] = pwm_end; + } else { + ins->std.dutyMacro.len = 1; + ins->std.dutyMacro.val[0] = pwm_end; // sequence mode, set last value + } + + if (pwm_mode == 1 || pwm_mode == 2) { + ins->std.dutyMacro.open = 4 | 1; // LFO + ins->std.dutyMacro.mode = 2; // LFO + + ins->std.dutyMacro.val[11] = (int)((uint64_t)pwm_speed * (uint64_t)40 * (uint64_t)abs((int)(pwm_start - pwm_end)) / (2048 - 311) / 69); // LFO speed + ins->std.dutyMacro.val[12] = 1; // sawtooth LFO wave + + if (pwm_mode == 2) { + ins->std.dutyMacro.val[11] = (int)((uint64_t)pwm_speed * (uint64_t)40 * (uint64_t)abs((int)(pwm_start - pwm_end)) / (2048 - 311) / 69 / 2); // LFO speed multiplied by 2 + ins->std.dutyMacro.val[12] = 0; // triangle LFO wave + } + } + + if (pwm_mode == 3) { + ins->std.dutyMacro.open = 2 | 1; // ADSR + ins->std.dutyMacro.mode = 1; // LFO + ins->std.dutyMacro.val[2] = (int)((uint64_t)pwm_speed * (uint64_t)11 * (uint64_t)abs((int)(pwm_start - pwm_end)) / (2048 - 311) / 69); // ADSR attack rate + } + } + + seek_amount -= 10; + } + + if (instVersion >= 3) { + unsigned int filter_start = reader.readI(); + unsigned int filter_end = reader.readI(); + + unsigned char filter_speed = reader.readC(); + unsigned char filter_mode = reader.readC(); + + if (filter_mode == 0) { + seek_amount -= 10; + } else { + ins->c64.filterIsAbs = true; + + // modes: 1 = saw lfo, 2 = tri lfo, 3 = oneshot ADSR, 4 = set val? + + ins->std.algMacro.len = 18; + + if (filter_mode != 4) { + ins->std.algMacro.val[0] = filter_start; + ins->std.algMacro.val[1] = filter_end; + } else { + ins->std.algMacro.len = 1; + ins->std.algMacro.val[0] = filter_end; // sequence mode, set last value + } + + if (filter_mode == 1 || filter_mode == 2) { + ins->std.algMacro.open = 4 | 1; // LFO + ins->std.algMacro.mode = 2; // LFO + + ins->std.algMacro.val[11] = (int)((uint64_t)filter_speed * (uint64_t)40 * (uint64_t)abs((int)(filter_start - filter_end)) / (2048 - 311) / 69); // LFO speed + ins->std.algMacro.val[12] = 1; // sawtooth LFO wave + + if (filter_mode == 2) { + ins->std.algMacro.val[11] = (int)((uint64_t)filter_speed * (uint64_t)40 * (uint64_t)abs((int)(filter_start - filter_end)) / (2048 - 311) / 69 / 2); // LFO speed multiplied by 2 + ins->std.algMacro.val[12] = 0; // triangle LFO wave + } + } + + if (filter_mode == 3) { + ins->std.algMacro.open = 2 | 1; // ADSR + ins->std.algMacro.mode = 1; // LFO + ins->std.algMacro.val[2] = (int)((uint64_t)filter_speed * (uint64_t)11 * (uint64_t)abs((int)(filter_start - filter_end)) / (2048 - 311) / 69); // ADSR attack rate + } + } + } + + if (instVersion >= 4) { + unsigned int totalSeqs = reader.readI(); + seek_amount -= 4; + if (totalSeqs > 5) { + logE("%d: too many sequences!", insIndex); + lastError = "too many sequences"; + delete[] file; + return false; + } + + for (unsigned int j = 0; j < totalSeqs; j++) { + hasSequence[insIndex][j] = reader.readC(); + sequenceIndex[insIndex][j] = reader.readC(); + + seek_amount -= 2; + } + } + + if (instVersion == 2) { + reader.seek(seek_amount, SEEK_CUR); // what the fuck + } + + /*for(int tti = 0; tti < 20; tti++) + { + char aaaa = reader.readC(); + logV("\'%c\'", aaaa); + }*/ + } else { + reader.seek(-4, SEEK_CUR); + } + + break; + } + default: { - logE("%d: what's going on here?",insIndex); - lastError="invalid instrument type"; + logE("%d: what's going on here?", insIndex); + lastError = "invalid instrument type"; delete[] file; return false; } } // name - ins->name=reader.readString((unsigned int)reader.readI()); - logV("- %d: %s",insIndex,ins->name); + ins->name = reader.readString((unsigned int)reader.readI()); + logV("- %d: %s", insIndex, ins->name); } - */ - } else if (blockName=="SEQUENCES") { + + ds.insLen = 128; + + } else if (blockName == "SEQUENCES") { CHECK_BLOCK_VERSION(6); - reader.seek(blockSize,SEEK_CUR); - } else if (blockName=="FRAMES") { - CHECK_BLOCK_VERSION(3); + // reader.seek(blockSize,SEEK_CUR); - for (size_t i=0; iordersLen=reader.readI(); - if (blockVersion>=3) { - s->speeds.val[0]=reader.readI(); - } - if (blockVersion>=2) { - s->virtualTempoN=reader.readI(); - s->patLen=reader.readI(); - } - int why=tchans; - if (blockVersion==1) { - why=reader.readI(); - } - logV("reading %d and %d orders",tchans,s->ordersLen); + unsigned char* Indices = new unsigned char[128 * 5]; + unsigned char* Types = new unsigned char[128 * 5]; - for (int j=0; jordersLen; j++) { - for (int k=0; korders.ord[k][j]=o; + unsigned int seq_count = reader.readI(); + + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int index = reader.readI(); + Indices[i] = index; + unsigned int type = reader.readI(); + Types[i] = type; + + unsigned char size = reader.readC(); + unsigned int setting = 0; + + macros[index][type].len = size; + + unsigned int loop = reader.readI(); + + macros[index][type].loop = loop; + + if (blockVersion == 4) { + unsigned int release = reader.readI(); + setting = reader.readI(); + + macros[index][type].rel = release; + macro_types[index][type] = setting; + } + + for (int j = 0; j < size; j++) { + unsigned char seq = reader.readC(); + macros[index][type].val[j] = seq; + } + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_NES && hasSequence[k][Types[i]]) { + copyMacro(ins, ¯os[index][type], Types[i], setting); + // memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro)); } } } - } else if (blockName=="PATTERNS") { - CHECK_BLOCK_VERSION(6); - size_t blockEnd=reader.tell()+blockSize; + if (blockVersion == 5) // Version 5 saved the release points incorrectly, this is fixed in ver 6 + { + for (int i = 0; i < 128; i++) { + for (int j = 0; j < 5; j++) { + unsigned int release = reader.readI(); + unsigned int setting = reader.readI(); - if (blockVersion==1) { - int patLenOld=reader.readI(); - for (DivSubSong* i: ds.subsong) { - i->patLen=patLenOld; - } - } + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][j] == i && ins->type == DIV_INS_NES && hasSequence[k][j]) { + macros[k][j].rel = release; + macro_types[k][j] = setting; - // so it appears .ftm doesn't keep track of how many patterns are stored in the file.... - while (reader.tell()=2) subs=reader.readI(); - int ch=reader.readI(); - int patNum=reader.readI(); - int numRows=reader.readI(); - - DivPattern* pat=ds.subsong[subs]->pat[ch].getPattern(patNum,true); - for (int i=0; i=2 && blockVersion<6) { // row index - row=reader.readI(); - } else { - row=reader.readC(); - } - - unsigned char nextNote=reader.readC(); - unsigned char nextOctave=reader.readC(); - if (nextNote==0x0d) { - pat->data[row][0]=100; - } else if (nextNote==0x0e) { - pat->data[row][0]=101; - } else if (nextNote==0x01) { - pat->data[row][0]=12; - pat->data[row][1]=nextOctave-1; - } else if (nextNote==0) { - pat->data[row][0]=0; - } else if (nextNote<0x0d) { - pat->data[row][0]=nextNote-1; - pat->data[row][1]=nextOctave; - } - - unsigned char nextIns=reader.readC(); - if (nextIns<0x40) { - pat->data[row][2]=nextIns; - } else { - pat->data[row][2]=-1; - } - - unsigned char nextVol=reader.readC(); - if (nextVol<0x10) { - pat->data[row][3]=nextVol; - } else { - pat->data[row][3]=-1; - } - - int effectCols=ds.subsong[subs]->pat[ch].effectCols; - if (blockVersion>=6) effectCols=4; - - for (int j=0; jdata[row][4+(j*2)]=-1; - pat->data[row][5+(j*2)]=-1; - } else { - if (nextEffectdata[row][4+(j*2)]=ftEffectMap[nextEffect]; - } else { - pat->data[row][4+(j*2)]=-1; + copyMacro(ins, ¯os[sequenceIndex[k][j]][j], j, setting); + // memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)j, true), ¯os[sequenceIndex[k][j]][j], sizeof(DivInstrumentMacro)); } - pat->data[row][5+(j*2)]=nextEffectVal; } } } } - } else if (blockName=="DPCM SAMPLES") { - CHECK_BLOCK_VERSION(1); - reader.seek(blockSize,SEEK_CUR); - } else if (blockName=="SEQUENCES_VRC6") { - // where are the 5B and FDS sequences? + + if (blockVersion >= 6) // Read release points correctly stored + { + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int release = reader.readI(); + unsigned int setting = reader.readI(); + + // macros[index][type].rel = release; + // macro_types[index][type] = setting; + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_NES && hasSequence[k][Types[i]]) { + macros[sequenceIndex[k][Types[i]]][Types[i]].rel = release; + macro_types[k][Types[i]] = setting; + + copyMacro(ins, ¯os[sequenceIndex[k][Types[i]]][Types[i]], Types[i], setting); + // memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[k][Types[i]]][Types[i]], sizeof(DivInstrumentMacro)); + } + } + } + } + + delete[] Indices; + delete[] Types; + } else if (blockName == "GROOVES") { CHECK_BLOCK_VERSION(6); - reader.seek(blockSize,SEEK_CUR); - } else if (blockName=="SEQUENCES_N163") { + // reader.seek(blockSize,SEEK_CUR); + + unsigned char num_grooves = reader.readC(); + int max_groove = 0; + + for (int i = 0; i < 0xff; i++) { + ds.grooves.push_back(DivGroovePattern()); + } + + for (int gr = 0; gr < num_grooves; gr++) { + unsigned char index = reader.readC(); + unsigned char size = reader.readC(); + + if (index > max_groove) + max_groove = index + 1; + + DivGroovePattern gp; + gp.len = size; + + for (int sz = 0; sz < size; sz++) { + unsigned char value = reader.readC(); + gp.val[sz] = value; + } + + ds.grooves[index] = gp; + } + + ds.grooves.resize(max_groove == 0 ? 1 : max_groove); + + unsigned char subsongs = reader.readC(); + + for (int sub = 0; sub < subsongs; sub++) { + unsigned char used = reader.readC(); + + if (used) { + ds.subsong[sub]->speeds = ds.grooves[0]; + } + } + + if ((reader.tell() - blockStart) != blockSize) { + logE("block %s size does not match! block size %d curr pos %d", blockName, blockSize, reader.tell() - blockStart); + } + + } else if (blockName == "FRAMES") { + CHECK_BLOCK_VERSION(3); + + for (size_t i = 0; i < ds.subsong.size(); i++) { + DivSubSong* s = ds.subsong[i]; + + s->ordersLen = reader.readI(); + if (blockVersion >= 3) { + s->speeds.val[0] = reader.readI(); + } + if (blockVersion >= 2) { + int temp = s->virtualTempoN; + int tempo = reader.readI(); + + logV("tempo %d", tempo); + + if (tempo == 0) { + s->virtualTempoN = 150.0; // TODO: make it properly + } else { + s->virtualTempoN = (short)((float)temp * (float)tempo / 150.0); + } + + s->patLen = reader.readI(); + } + int why = tchans; + if (blockVersion == 1) { + why = reader.readI(); + } + logV("reading %d and %d orders", tchans, s->ordersLen); + + for (int j = 0; j < s->ordersLen; j++) { + for (int k = 0; k < why; k++) { + unsigned char o = reader.readC(); + // logV("%.2x",o); + s->orders.ord[map_channels[k]][j] = o; + } + } + } + } else if (blockName == "PATTERNS") { + CHECK_BLOCK_VERSION(6); + + size_t blockEnd = reader.tell() + blockSize; + + if (blockVersion == 1) { + int patLenOld = reader.readI(); + for (DivSubSong* i : ds.subsong) { + i->patLen = patLenOld; + } + } + + // so it appears .ftm doesn't keep track of how many patterns are stored in the file.... + while (reader.tell() < blockEnd) { + logV("reading pattern at %x...",reader.tell()); + int subs = 0; + if (blockVersion >= 2) + subs = reader.readI(); + int ch = reader.readI(); + int patNum = reader.readI(); + int numRows = reader.readI(); + + /* + logV("ch: %d",ch); + logV("subs: %d. map_channels[ch]: %d",subs,map_channels[ch]); + logV("patNum: %d",patNum); + logV("rows: %d",numRows); + */ + + DivPattern* pat = ds.subsong[subs]->pat[map_channels[ch]].getPattern(patNum, true); + for (int i = 0; i < numRows; i++) { + unsigned int row = 0; + if (blockVersion >= 2 && blockVersion < 6) { // row index + row = reader.readI(); + } else { + row = (unsigned char)reader.readC(); + } + + unsigned char nextNote = reader.readC(); + unsigned char nextOctave = reader.readC(); + + if (blockVersion < 5 && map_channels[ch] == fds_chan) // FDS transpose + { + nextOctave += 2; + nextOctave -= 2; + } + if (blockVersion >= 5 && map_channels[ch] == fds_chan) // FDS transpose + { + nextOctave -= 2; + } + + if (map_channels[ch] != 0xff) { + if (nextNote == 0x0d) { + pat->data[row][0] = 101; + } else if (nextNote == 0x0e) { + pat->data[row][0] = 100; + } else if (nextNote == 0x01) { + pat->data[row][0] = 12; + pat->data[row][1] = nextOctave - 1; + } else if (nextNote == 0) { + pat->data[row][0] = 0; + } else if (nextNote < 0x0d) { + pat->data[row][0] = nextNote - 1; + pat->data[row][1] = nextOctave; + } + } + + unsigned char nextIns = reader.readC(); + if (map_channels[ch] != 0xff) { + if (nextIns < 0x40 && nextNote != 0x0d && nextNote != 0x0e) { + pat->data[row][2] = nextIns; + } else { + pat->data[row][2] = -1; + } + } + + unsigned char nextVol = reader.readC(); + if (map_channels[ch] != 0xff) { + if (nextVol < 0x10) { + pat->data[row][3] = nextVol; + if (map_channels[ch] == vrc6_saw_chan) // scale volume + { + pat->data[row][3] = pat->data[row][3] * 42 / 15; + } + + if (map_channels[ch] == fds_chan) { + pat->data[row][3] = pat->data[row][3] * 31 / 15; + } + } else { + pat->data[row][3] = -1; + } + } + + int effectCols = ds.subsong[subs]->pat[map_channels[ch]].effectCols; + if (blockVersion >= 6) + effectCols = 4; + + if (ds.version == 0x020) { + effectCols = 1; + } + + unsigned char nextEffectVal = 0; + unsigned char nextEffect = 0; + + //logV("row %d effects are read at %x",row,reader.tell()); + + for (int j = 0; j < effectCols; j++) { + nextEffect = reader.readC(); + + if (nextEffect>0 && ((nextEffect < FT_EF_COUNT && !eft) || (nextEffect < EFT_EF_COUNT && eft))) { + nextEffectVal = reader.readC(); + + if (blockVersion < 3) { + if (nextEffect == FT_EF_PORTAOFF) { + nextEffect = FT_EF_PORTAMENTO; + nextEffectVal = 0; + } else if (nextEffect == FT_EF_PORTAMENTO) { + if (nextEffect < 0xFF) + nextEffectVal++; + } + } + } else if (blockVersion < 6) { + nextEffectVal = reader.readC(); + } + + // Specific for version 2.0 + if (ds.version == 0x0200 && j == 0) { + if (nextEffect == FT_EF_SPEED && nextEffectVal < 20) + nextEffectVal++; + + if (pat->data[row][3] == 0) + pat->data[row][3] = 0xf; + else { + pat->data[row][3]--; + pat->data[row][3] &= 0x0F; + } + + if (pat->data[row][0] == 0) + pat->data[row][2] = -1; + } + + if (blockVersion == 3) { + // Fix for VRC7 portamento + bool is_vrc7 = false; + + for (int vrr = 0; vrr < 6; vrr++) { + if (map_channels[ch] == vrc7_chans[vrr]) { + is_vrc7 = true; + } + } + + if (is_vrc7) { + switch (nextEffect) { + case FT_EF_PORTA_DOWN: + nextEffect = FT_EF_PORTA_UP; + break; + case FT_EF_PORTA_UP: + nextEffect = FT_EF_PORTA_DOWN; + break; + default: + break; + } + } + // FDS pitch effect fix + else if (map_channels[ch] == fds_chan) { + switch (nextEffect) { + case FT_EF_PITCH: + if (nextEffectVal != 0x80) + nextEffectVal = (0x100 - nextEffectVal) & 0xFF; + break; + default: + break; + } + } + } + + for (int v = 0; v < 8; v++) { + if (map_channels[ch] == n163_chans[v]) { + if (nextEffect == FT_EF_SAMPLE_OFFSET) { + nextEffect = FT_EF_N163_WAVE_BUFFER; + } + } + } + + if (ds.version < 0x0450 || dnft) { + unsigned char idx = 0; + + while (eff_conversion_050[idx][0] != 0xFF) // until the end of the array + { + if (nextEffect == eff_conversion_050[idx][0]) // remap the effects (0CC vs FT effects type order) bruh idk why but it should be done to correctly read some modules + { + nextEffect = eff_conversion_050[idx][1]; + + break; + } + + idx++; + } + + } + + // logW("next effect %d val %d", nextEffect, nextEffectVal); + + if (map_channels[ch] != 0xff) { + if (nextEffect == 0 && nextEffectVal == 0) { + pat->data[row][4 + (j * 2)] = -1; + pat->data[row][5 + (j * 2)] = -1; + } else { + if (nextEffect < ftEffectMapSize) { + if (eft) { + pat->data[row][4 + (j * 2)] = eftEffectMap[nextEffect]; + pat->data[row][5 + (j * 2)] = eftEffectMap[nextEffect] == -1 ? -1 : nextEffectVal; + + if (pat->data[row][4 + (j * 2)] == 0x100) { + pat->data[row][3] += pat->data[row][5 + (j * 2)] ? 0x10 : 0; // extra volume bit for AY8930 + pat->data[row][4 + (j * 2)] = -1; + pat->data[row][5 + (j * 2)] = -1; + } + + if (eftEffectMap[nextEffect] == 0x0f && nextEffectVal > 0x1f) { + pat->data[row][4 + (j * 2)] = 0xfd; // BPM speed change! + } + + if ((eftEffectMap[nextEffect] == 0xe1 || eftEffectMap[nextEffect] == 0xe2) && (nextEffectVal & 0xf0) == 0) { + pat->data[row][5 + (j * 2)] |= 0x10; // in FamiTracker if e1/e2 commands speed is 0 the portamento still has some speed! + } + } else { + pat->data[row][4 + (j * 2)] = ftEffectMap[nextEffect]; + pat->data[row][5 + (j * 2)] = ftEffectMap[nextEffect] == -1 ? -1 : nextEffectVal; + + if (ftEffectMap[nextEffect] == 0x0f && nextEffectVal > 0x1f) { + pat->data[row][4 + (j * 2)] = 0xfd; // BPM speed change! + } + + if ((ftEffectMap[nextEffect] == 0xe1 || ftEffectMap[nextEffect] == 0xe2) && (nextEffectVal & 0xf0) == 0) { + pat->data[row][5 + (j * 2)] |= 0x10; // in FamiTracker if e1/e2 commands speed is 0 the portamento still has some speed! + } + } + for (int v = 0; v < 8; v++) { + if (map_channels[ch] == n163_chans[v]) { + if (pat->data[row][4 + (j * 2)] == 0x12) { + pat->data[row][4 + (j * 2)] = 0x17; // local wave + } + } + } + } else { + pat->data[row][4 + (j * 2)] = -1; + pat->data[row][5 + (j * 2)] = -1; + } + } + } + } + } + } + } else if (blockName == "DPCM SAMPLES") { CHECK_BLOCK_VERSION(1); - reader.seek(blockSize,SEEK_CUR); - } else if (blockName=="COMMENTS") { + // reader.seek(blockSize,SEEK_CUR); + unsigned char num_samples = reader.readC(); + + for (int i = 0; i < 256; i++) { + DivSample* s = new DivSample(); + ds.sample.push_back(s); + } + + ds.sampleLen = ds.sample.size(); + + unsigned int true_size = 0; + unsigned char index = 0; + + for (unsigned char i = 0; i < num_samples; i++) { + index = reader.readC(); + + DivSample* sample = ds.sample[index]; + + sample->rate = 33144; + sample->centerRate = 33144; + sample->depth = DIV_SAMPLE_DEPTH_1BIT_DPCM; + + sample->name = reader.readString((unsigned int)reader.readI()); + + unsigned int sample_len = reader.readI(); + + true_size = sample_len + ((1 - (int)sample_len) & 0x0f); + sample->lengthDPCM = true_size; + sample->samples = true_size * 8; + + sample->dataDPCM = new unsigned char[true_size]; + + memset(sample->dataDPCM, 0xAA, true_size); + + reader.read(sample->dataDPCM, true_size); + } + + int last_non_empty_sample = 0xff; + + for (int i = 255; i > 0; i--) { + DivSample* s = ds.sample[i]; + + if (s->dataDPCM) { + last_non_empty_sample = i; + break; + } + } + + for (int i = 255; i > last_non_empty_sample; i--) { + ds.sample.erase(ds.sample.begin() + i); + } + + ds.sampleLen = ds.sample.size(); + + } else if (blockName == "SEQUENCES_VRC6") { + CHECK_BLOCK_VERSION(6); + + unsigned char* Indices = new unsigned char[128 * 5]; + unsigned char* Types = new unsigned char[128 * 5]; + + unsigned int seq_count = reader.readI(); + + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int index = reader.readI(); + Indices[i] = index; + unsigned int type = reader.readI(); + Types[i] = type; + + unsigned char size = reader.readC(); + unsigned int setting = 0; + + macros[index][type].len = size; + + unsigned int loop = reader.readI(); + + macros[index][type].loop = loop; + + if (blockVersion == 4) { + unsigned int release = reader.readI(); + setting = reader.readI(); + + macros[index][type].rel = release; + macro_types[index][type] = setting; + } + + for (int j = 0; j < size; j++) { + unsigned char seq = reader.readC(); + macros[index][type].val[j] = seq; + } + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_VRC6 && hasSequence[k][Types[i]]) { + copyMacro(ins, ¯os[index][type], type, setting); + + if (type == 0 && setting == 1) { + ins->type = DIV_INS_VRC6_SAW; + } + } + } + } + + if (blockVersion == 5) // Version 5 saved the release points incorrectly, this is fixed in ver 6 + { + for (int i = 0; i < 128; i++) { + for (int j = 0; j < 5; j++) { + unsigned int release = reader.readI(); + unsigned int setting = reader.readI(); + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][j] == i && ins->type == DIV_INS_VRC6 && hasSequence[k][j]) { + macros[k][j].rel = release; + macro_types[k][j] = setting; + + copyMacro(ins, ¯os[sequenceIndex[k][j]][j], j, setting); + + if (j == 0 && setting == 1) { + ins->type = DIV_INS_VRC6_SAW; + } + } + } + } + } + } + + if (blockVersion >= 6) // Read release points correctly stored + { + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int release = reader.readI(); + unsigned int setting = reader.readI(); + + // macros[index][type].rel = release; + // macro_types[index][type] = setting; + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_VRC6 && hasSequence[k][Types[i]]) { + macros[sequenceIndex[k][Types[i]]][Types[i]].rel = release; + macro_types[k][Types[i]] = setting; + + copyMacro(ins, ¯os[sequenceIndex[k][Types[i]]][Types[i]], Types[i], setting); + + if (Types[i] == 0 && setting == 1) { + ins->type = DIV_INS_VRC6_SAW; + } + } + } + } + } + + delete[] Indices; + delete[] Types; + } else if (blockName == "SEQUENCES_N163" || blockName == "SEQUENCES_N106") { CHECK_BLOCK_VERSION(1); - reader.seek(blockSize,SEEK_CUR); + // reader.seek(blockSize,SEEK_CUR); + + unsigned char* Indices = new unsigned char[128 * 5]; + unsigned char* Types = new unsigned char[128 * 5]; + + unsigned int seq_count = reader.readI(); + + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int index = reader.readI(); + Indices[i] = index; + unsigned int type = reader.readI(); + Types[i] = type; + + unsigned char size = reader.readC(); + unsigned int setting = 0; + + macros[index][type].len = size; + + unsigned int loop = reader.readI(); + + macros[index][type].loop = loop; + + unsigned int release = reader.readI(); + setting = reader.readI(); + + macros[index][type].rel = release; + macro_types[index][type] = setting; + + for (int j = 0; j < size; j++) { + unsigned char seq = reader.readC(); + macros[index][type].val[j] = seq; + } + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_N163 && hasSequence[k][Types[i]]) { + copyMacro(ins, ¯os[index][type], type, setting); + // memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro)); + } + } + } + + delete[] Indices; + delete[] Types; + + } else if (blockName == "SEQUENCES_S5B") { + CHECK_BLOCK_VERSION(1); + // reader.seek(blockSize,SEEK_CUR); + + unsigned char* Indices = new unsigned char[128 * 5]; + unsigned char* Types = new unsigned char[128 * 5]; + + unsigned int seq_count = reader.readI(); + + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int index = reader.readI(); + Indices[i] = index; + unsigned int type = reader.readI(); + Types[i] = type; + + unsigned char size = reader.readC(); + unsigned int setting = 0; + + macros[index][type].len = size; + + unsigned int loop = reader.readI(); + + macros[index][type].loop = loop; + + unsigned int release = reader.readI(); + setting = reader.readI(); + + macros[index][type].rel = release; + macro_types[index][type] = setting; + + for (int j = 0; j < size; j++) { + unsigned char seq = reader.readC(); + macros[index][type].val[j] = seq; + } + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][type] == Indices[i] && ins->type == DIV_INS_AY && hasSequence[k][type]) { + copyMacro(ins, ¯os[index][type], type, setting); + // memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro)); + } + } + } + + delete[] Indices; + delete[] Types; + } else if (blockName == "SEQUENCES_SID") { + CHECK_BLOCK_VERSION(4); + + unsigned char* Indices = new unsigned char[128 * 5]; + unsigned char* Types = new unsigned char[128 * 5]; + + unsigned int seq_count = reader.readI(); + + for (unsigned int i = 0; i < seq_count; i++) { + unsigned int index = reader.readI(); + Indices[i] = index; + unsigned int type = reader.readI(); + Types[i] = type; + + unsigned char size = reader.readC(); + unsigned int setting = 0; + + macros[index][type].len = size; + + unsigned int loop = reader.readI(); + + macros[index][type].loop = loop; + + unsigned int release = reader.readI(); + setting = reader.readI(); + + macros[index][type].rel = release; + macro_types[index][type] = setting; + + for (int j = 0; j < size; j++) { + unsigned char seq = reader.readC(); + macros[index][type].val[j] = seq; + } + + for (int k = 0; k < (int)ds.ins.size(); k++) { + DivInstrument* ins = ds.ins[k]; + if (sequenceIndex[k][type] == Indices[i] && ins->type == DIV_INS_C64 && hasSequence[k][type]) { + copyMacro(ins, ¯os[index][type], type, setting); + // memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro)); + } + } + } + + delete[] Indices; + delete[] Types; + } else if (blockName == "JSON") { + CHECK_BLOCK_VERSION(1); + reader.seek(blockSize, SEEK_CUR); + } else if (blockName == "PARAMS_EMU") { + CHECK_BLOCK_VERSION(1); + reader.seek(blockSize, SEEK_CUR); + } else if (blockName == "DETUNETABLES") { + CHECK_BLOCK_VERSION(1); + reader.seek(blockSize, SEEK_CUR); + } else if (blockName == "COMMENTS") { + CHECK_BLOCK_VERSION(1); + // reader.seek(blockSize,SEEK_CUR); + unsigned int display_comment = reader.readI(); + (void)display_comment; + + char ch = 1; + + do { + ch = reader.readC(); + String sss = String() + ch; + ds.subsong[0]->notes += sss; + } while (ch != 0); + + // ds.subsong[0]->notes = reader.readS(); + } else if (blockName == "PARAMS_EXTRA") { + CHECK_BLOCK_VERSION(3); + // reader.seek(blockSize,SEEK_CUR); + unsigned int linear_pitch = reader.readI(); + + ds.linearPitch = linear_pitch == 0 ? 0 : 2; + + if (blockVersion >= 2) { + int fineTuneCents = reader.readC() * 100; + fineTuneCents += reader.readC(); + + ds.tuning = 440.0 * pow(2.0, (double)fineTuneCents / 1200.0); + } + if (blockVersion >= 3) { + unsigned char flats = reader.readC(); + (void)flats; + } + } else if (blockName == "TUNING") { + CHECK_BLOCK_VERSION(1); + // reader.seek(blockSize,SEEK_CUR); + if (blockVersion == 1) { + int fineTuneCents = reader.readC() * 100; + fineTuneCents += reader.readC(); + + ds.tuning = 440.0 * pow(2.0, (double)fineTuneCents / 1200.0); + } + } else if (blockName == "BOOKMARKS") { + CHECK_BLOCK_VERSION(1); + reader.seek(blockSize, SEEK_CUR); } else { - logE("block %s is unknown!",blockName); - lastError="unknown block "+blockName; + logE("block %s is unknown!", blockName); + lastError = "unknown block " + blockName; delete[] file; return false; } - if ((reader.tell()-blockStart)!=blockSize) { - logE("block %s is incomplete!",blockName); - lastError="incomplete block "+blockName; + if ((reader.tell() - blockStart) != blockSize) { + logE("block %s is incomplete! reader.tell()-blockStart %d blockSize %d", blockName, (reader.tell() - blockStart), blockSize); + lastError = "incomplete block " + blockName; delete[] file; return false; } } - addWarning("FamiTracker import is experimental!"); + if (ds.insLen > 0) { + for (int tries = 0; tries < 5; tries++) // de-duplicating instruments + { + for (int i = 0; i < 128; i++) { + int index = i >= (int)ds.insLen ? ((int)ds.insLen - 1) : i; + if (index < 0) + index = 0; + DivInstrument* ins = ds.ins[index]; + + if (ins->type == DIV_INS_FM) { + delete ds.ins[index]; + ds.ins.erase(ds.ins.begin() + index); + ds.insLen = ds.ins.size(); + 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][2] > index) { + ds.subsong[j]->pat[ii].data[k]->data[l][2]--; + } + } + } + } + } + } + } + } + } + + // 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. + + // 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. + + /* + int ins_vrc6_conv[256][2]; + int ins_vrc6_saw_conv[256][2]; + int ins_nes_conv[256][2]; // vrc6 (or whatever) -> nes + + for (int i = 0; i < 256; i++) { + ins_vrc6_conv[i][0] = -1; + ins_vrc6_conv[i][1] = -1; + + ins_vrc6_saw_conv[i][0] = -1; + ins_vrc6_saw_conv[i][1] = -1; + + ins_nes_conv[i][0] = -1; + ins_nes_conv[i][1] = -1; + } + + int init_inst_num = ds.ins.size(); + + for (int i = 0; i < init_inst_num; i++) { + 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][2] == i) // instrument + { + DivInstrument* ins = ds.ins[i]; + bool go_to_end = false; + + if (ins->type != DIV_INS_VRC6 && (ii == vrc6_chans[0] || ii == vrc6_chans[1])) // we encountered non-VRC6 instrument on VRC6 channel + { + DivInstrument* insnew = new DivInstrument; + ds.ins.push_back(insnew); + + copyInstrument(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; + ds.ins[ds.ins.size() - 1]->amiga.useNoteMap = false; + + 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]; + } + } + } + + ins_vrc6_conv[i][0] = i; + ins_vrc6_conv[i][1] = ds.ins.size() - 1; + + go_to_end = true; + } + + if (go_to_end) { + goto end1; + } + } + } + } + } + } + + end1:; + } + + for (int i = 0; i < init_inst_num; i++) { + 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][2] == i) // instrument + { + DivInstrument* ins = ds.ins[i]; + bool go_to_end = false; + + if (ins->type != DIV_INS_VRC6_SAW && ii == vrc6_saw_chan) // we encountered non-VRC6-saw instrument on VRC6 saw channel + { + DivInstrument* insnew = new DivInstrument; + ds.ins.push_back(insnew); + + copyInstrument(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; + ds.ins[ds.ins.size() - 1]->amiga.useNoteMap = false; + + 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; + } + } + } + + ins_vrc6_saw_conv[i][0] = i; + ins_vrc6_saw_conv[i][1] = ds.ins.size() - 1; + + go_to_end = true; + } + + if (go_to_end) { + goto end2; + } + } + } + } + } + } + + end2:; + } + + for (int i = 0; i < init_inst_num; i++) { + 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][2] == i) // instrument + { + DivInstrument* ins = ds.ins[i]; + bool go_to_end = false; + + if (ins->type != DIV_INS_NES && (ii == mmc5_chans[0] || ii == mmc5_chans[1] || ii < 5)) // we encountered VRC6 (or whatever?) instrument on NES/MMC5 channel + { + DivInstrument* insnew = new DivInstrument; + ds.ins.push_back(insnew); + + copyInstrument(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 + { + 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]) { + case 0: + case 1: { + insnew->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] = 0; + break; + } + case 2: + case 3: + case 4: + case 5: { + insnew->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] = 1; + break; + } + case 6: + case 7: { + insnew->std.get_macro(DIV_MACRO_DUTY, false)->val[mm] = 2; + break; + } + default: + break; + } + } + } + } + + ins_nes_conv[i][0] = i; + ins_nes_conv[i][1] = ds.ins.size() - 1; + + go_to_end = true; + } + + if (go_to_end) { + goto end3; + } + } + } + } + } + } + + end3:; + } + + for (int i = 0; i < 256; i++) { + if (ins_vrc6_conv[i][0] != -1 || ins_vrc6_saw_conv[i][0] != -1 || ins_nes_conv[i][0] != -1) { + 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][2] == ins_vrc6_conv[i][0] && (ii == vrc6_chans[0] || ii == vrc6_chans[1])) // change ins index + { + ds.subsong[j]->pat[ii].data[k]->data[l][2] = ins_vrc6_conv[i][1]; + } + + if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == ins_vrc6_saw_conv[i][0] && ii == vrc6_saw_chan) { + ds.subsong[j]->pat[ii].data[k]->data[l][2] = ins_vrc6_saw_conv[i][1]; + } + + if (ds.subsong[j]->pat[ii].data[k]->data[l][2] == ins_nes_conv[i][0] && (ii == mmc5_chans[0] || ii == mmc5_chans[1] || ii < 5)) { + ds.subsong[j]->pat[ii].data[k]->data[l][2] = ins_nes_conv[i][1]; + } + } + } + } + } + } + } + */ + + ds.delayBehavior=0; ds.version=DIV_VERSION_FTM; + ds.insLen = ds.ins.size(); + ds.sampleLen = ds.sample.size(); + ds.waveLen = ds.wave.size(); if (active) quitDispatch(); BUSY_BEGIN_SOFT; @@ -601,11 +2394,10 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { } } catch (EndOfFileException& e) { logE("premature end of file!"); - lastError="incomplete file"; + lastError = "incomplete file"; delete[] file; return false; } delete[] file; return true; } - diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 8ad21b100..24126c340 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -259,6 +259,40 @@ bool DivInstrumentPowerNoise::operator==(const DivInstrumentPowerNoise& other) { #undef _C +#define CONSIDER(x,t) \ + case t: \ + return &x; \ + break; + +DivInstrumentMacro* DivInstrumentSTD::macroByType(DivMacroType type) { + switch (type) { + CONSIDER(volMacro,DIV_MACRO_VOL) + CONSIDER(arpMacro,DIV_MACRO_ARP) + CONSIDER(dutyMacro,DIV_MACRO_DUTY) + CONSIDER(waveMacro,DIV_MACRO_WAVE) + CONSIDER(pitchMacro,DIV_MACRO_PITCH) + CONSIDER(ex1Macro,DIV_MACRO_EX1) + CONSIDER(ex2Macro,DIV_MACRO_EX2) + CONSIDER(ex3Macro,DIV_MACRO_EX3) + CONSIDER(algMacro,DIV_MACRO_ALG) + CONSIDER(fbMacro,DIV_MACRO_FB) + CONSIDER(fmsMacro,DIV_MACRO_FMS) + CONSIDER(amsMacro,DIV_MACRO_AMS) + CONSIDER(panLMacro,DIV_MACRO_PAN_LEFT) + CONSIDER(panRMacro,DIV_MACRO_PAN_RIGHT) + CONSIDER(phaseResetMacro,DIV_MACRO_PHASE_RESET) + CONSIDER(ex4Macro,DIV_MACRO_EX4) + CONSIDER(ex5Macro,DIV_MACRO_EX5) + CONSIDER(ex6Macro,DIV_MACRO_EX6) + CONSIDER(ex7Macro,DIV_MACRO_EX7) + CONSIDER(ex8Macro,DIV_MACRO_EX8) + } + + return NULL; +} + +#undef CONSIDER + #define FEATURE_BEGIN(x) \ w->write(x,2); \ size_t featStartSeek=w->tell(); \ diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 714adeea5..fd75a4d0f 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -329,6 +329,9 @@ struct DivInstrumentSTD { damMacro(DIV_MACRO_OP_DAM), dvbMacro(DIV_MACRO_OP_DVB), egtMacro(DIV_MACRO_OP_EGT), kslMacro(DIV_MACRO_OP_KSL), susMacro(DIV_MACRO_OP_SUS), vibMacro(DIV_MACRO_OP_VIB), wsMacro(DIV_MACRO_OP_WS), ksrMacro(DIV_MACRO_OP_KSR) {} } opMacros[4]; + + DivInstrumentMacro* macroByType(DivMacroType type); + DivInstrumentSTD(): volMacro(DIV_MACRO_VOL,true), arpMacro(DIV_MACRO_ARP), diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d5fe08159..151dd93e9 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1637,7 +1637,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openLoad( "Open File", - {"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod *.fc", + {"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod *.fc *.ftm *.0cc *.dnm *.eft", "all files", "*"}, workingDirSong, dpiScale diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 8fdd1733f..cd9e72d7c 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -5654,6 +5654,9 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".smod",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ftm",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".0cc",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dnm",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".eft",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);