/** * Furnace Tracker - multi-system chiptune tracker * Copyright (C) 2021-2024 tildearrow and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "fileOpsCommon.h" short newFormatNotes[180]={ 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -5 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -4 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -3 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -2 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -1 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 0 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 1 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 2 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 3 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 4 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 5 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 6 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 7 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 8 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 // 9 }; short newFormatOctaves[180]={ 250, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, // -5 251, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, // -4 252, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, // -3 253, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, // -2 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // -1 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 2 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 3 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 4 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 5 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // 6 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 7 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 8 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 9 }; struct PatToWrite { unsigned short subsong, chan, pat; PatToWrite(unsigned short s, unsigned short c, unsigned short p): subsong(s), chan(c), pat(p) {} }; void DivEngine::putAssetDirData(SafeWriter* w, std::vector& dir) { size_t blockStartSeek, blockEndSeek; w->write("ADIR",4); blockStartSeek=w->tell(); w->writeI(0); w->writeI(dir.size()); for (DivAssetDir& i: dir) { w->writeString(i.name,false); w->writeS(i.entries.size()); for (int j: i.entries) { w->writeC(j); } } blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); w->writeI(blockEndSeek-blockStartSeek-4); w->seek(0,SEEK_END); } DivDataErrors DivEngine::readAssetDirData(SafeReader& reader, std::vector& dir) { char magic[4]; reader.read(magic,4); if (memcmp(magic,"ADIR",4)!=0) { logV("header is invalid: %c%c%c%c",magic[0],magic[1],magic[2],magic[3]); return DIV_DATA_INVALID_HEADER; } reader.readI(); // reserved unsigned int numDirs=reader.readI(); dir.reserve(numDirs); for (unsigned int i=0; i>4)&3) { case 0: newFlags.set("chipType",0); break; case 1: newFlags.set("chipType",1); break; case 2: newFlags.set("chipType",2); break; case 3: newFlags.set("chipType",3); break; } if (oldFlags&64) newFlags.set("stereo",true); if (oldFlags&128) newFlags.set("halfClock",true); newFlags.set("stereoSep",(int)((oldFlags>>8)&255)); break; case DIV_SYSTEM_AMIGA: if (oldFlags&1) newFlags.set("clockSel",1); if (oldFlags&2) newFlags.set("chipType",1); if (oldFlags&4) newFlags.set("bypassLimits",true); newFlags.set("stereoSep",(int)((oldFlags>>8)&127)); break; case DIV_SYSTEM_YM2151: switch (oldFlags&255) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; } break; case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: case DIV_SYSTEM_YM2612_DUALPCM: case DIV_SYSTEM_YM2612_DUALPCM_EXT: switch (oldFlags&0x7fffffff) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; case 3: newFlags.set("clockSel",3); break; case 4: newFlags.set("clockSel",4); break; } if (oldFlags&0x80000000) newFlags.set("ladderEffect",true); break; case DIV_SYSTEM_TIA: newFlags.set("clockSel",(int)(oldFlags&1)); switch ((oldFlags>>1)&3) { case 0: newFlags.set("mixingType",0); break; case 1: newFlags.set("mixingType",1); break; case 2: newFlags.set("mixingType",2); break; } break; case DIV_SYSTEM_VIC20: newFlags.set("clockSel",(int)(oldFlags&1)); break; case DIV_SYSTEM_SNES: newFlags.set("volScaleL",(int)(oldFlags&127)); newFlags.set("volScaleR",(int)((oldFlags>>8)&127)); break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: switch (oldFlags&15) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; case 3: newFlags.set("clockSel",3); break; } switch (oldFlags>>4) { case 0: newFlags.set("patchSet",0); break; case 1: newFlags.set("patchSet",1); break; case 2: newFlags.set("patchSet",2); break; case 3: newFlags.set("patchSet",3); break; } break; case DIV_SYSTEM_N163: switch (oldFlags&15) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; } newFlags.set("channels",(int)((oldFlags>>4)&7)); if (oldFlags&128) newFlags.set("multiplex",true); break; case DIV_SYSTEM_YM2203: case DIV_SYSTEM_YM2203_EXT: switch (oldFlags&31) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; case 3: newFlags.set("clockSel",3); break; case 4: newFlags.set("clockSel",4); break; case 5: newFlags.set("clockSel",5); break; } switch ((oldFlags>>5)&3) { case 0: newFlags.set("prescale",0); break; case 1: newFlags.set("prescale",1); break; case 2: newFlags.set("prescale",2); break; } break; case DIV_SYSTEM_YM2608: case DIV_SYSTEM_YM2608_EXT: switch (oldFlags&31) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; } switch ((oldFlags>>5)&3) { case 0: newFlags.set("prescale",0); break; case 1: newFlags.set("prescale",1); break; case 2: newFlags.set("prescale",2); break; } break; case DIV_SYSTEM_OPL: case DIV_SYSTEM_OPL2: case DIV_SYSTEM_Y8950: case DIV_SYSTEM_OPL_DRUMS: case DIV_SYSTEM_OPL2_DRUMS: case DIV_SYSTEM_Y8950_DRUMS: case DIV_SYSTEM_YMZ280B: switch (oldFlags&0xff) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; case 3: newFlags.set("clockSel",3); break; case 4: newFlags.set("clockSel",4); break; case 5: newFlags.set("clockSel",5); break; } break; case DIV_SYSTEM_OPL3: case DIV_SYSTEM_OPL3_DRUMS: switch (oldFlags&0xff) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; case 3: newFlags.set("clockSel",3); break; case 4: newFlags.set("clockSel",4); break; } break; case DIV_SYSTEM_PCSPKR: newFlags.set("speakerType",(int)(oldFlags&3)); break; case DIV_SYSTEM_RF5C68: switch (oldFlags&15) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; } switch (oldFlags>>4) { case 0: newFlags.set("chipType",0); break; case 1: newFlags.set("chipType",1); break; } break; case DIV_SYSTEM_VRC7: switch (oldFlags&15) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; case 3: newFlags.set("clockSel",3); break; } break; case DIV_SYSTEM_SFX_BEEPER: case DIV_SYSTEM_SFX_BEEPER_QUADTONE: newFlags.set("clockSel",(int)(oldFlags&1)); break; case DIV_SYSTEM_SCC: case DIV_SYSTEM_SCC_PLUS: switch (oldFlags&63) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; case 3: newFlags.set("clockSel",3); break; } break; case DIV_SYSTEM_MSM6295: switch (oldFlags&63) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; case 3: newFlags.set("clockSel",3); break; case 4: newFlags.set("clockSel",4); break; case 5: newFlags.set("clockSel",5); break; case 6: newFlags.set("clockSel",6); break; case 7: newFlags.set("clockSel",7); break; case 8: newFlags.set("clockSel",8); break; case 9: newFlags.set("clockSel",9); break; case 10: newFlags.set("clockSel",10); break; case 11: newFlags.set("clockSel",11); break; case 12: newFlags.set("clockSel",12); break; case 13: newFlags.set("clockSel",13); break; case 14: newFlags.set("clockSel",14); break; } if (oldFlags&128) newFlags.set("rateSel",true); break; case DIV_SYSTEM_MSM6258: switch (oldFlags) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; case 3: newFlags.set("clockSel",3); break; } break; case DIV_SYSTEM_OPL4: case DIV_SYSTEM_OPL4_DRUMS: switch (oldFlags&0xff) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; case 2: newFlags.set("clockSel",2); break; } break; case DIV_SYSTEM_X1_010: switch (oldFlags&15) { case 0: newFlags.set("clockSel",0); break; case 1: newFlags.set("clockSel",1); break; } if (oldFlags&16) newFlags.set("stereo",true); break; case DIV_SYSTEM_SOUND_UNIT: newFlags.set("clockSel",(int)(oldFlags&1)); if (oldFlags&4) newFlags.set("echo",true); if (oldFlags&8) newFlags.set("swapEcho",true); newFlags.set("sampleMemSize",(int)((oldFlags>>4)&1)); if (oldFlags&32) newFlags.set("pdm",true); newFlags.set("echoDelay",(int)((oldFlags>>8)&63)); newFlags.set("echoFeedback",(int)((oldFlags>>16)&15)); newFlags.set("echoResolution",(int)((oldFlags>>20)&15)); newFlags.set("echoVol",(int)((oldFlags>>24)&255)); break; case DIV_SYSTEM_PCM_DAC: if (!oldFlags) oldFlags=0x1f0000|44099; newFlags.set("rate",(int)((oldFlags&0xffff)+1)); newFlags.set("outDepth",(int)((oldFlags>>16)&15)); if (oldFlags&0x100000) newFlags.set("stereo",true); break; case DIV_SYSTEM_QSOUND: newFlags.set("echoDelay",(int)(oldFlags&0xfff)); newFlags.set("echoFeedback",(int)((oldFlags>>12)&255)); break; default: break; } } bool DivEngine::loadFur(unsigned char* file, size_t len) { unsigned int insPtr[256]; unsigned int wavePtr[256]; unsigned int samplePtr[256]; unsigned int subSongPtr[256]; unsigned int sysFlagsPtr[DIV_MAX_CHIPS]; unsigned int assetDirPtr[3]; std::vector patPtr; int numberOfSubSongs=0; char magic[5]; memset(magic,0,5); SafeReader reader=SafeReader(file,len); warnings=""; assetDirPtr[0]=0; assetDirPtr[1]=0; assetDirPtr[2]=0; try { DivSong ds; DivSubSong* subSong=ds.subsong[0]; if (!reader.seek(16,SEEK_SET)) { logE("premature end of file!"); lastError="incomplete file"; delete[] file; return false; } ds.version=reader.readS(); logI("module version %d (0x%.2x)",ds.version,ds.version); if (ds.version>DIV_ENGINE_VERSION) { logW("this module was created with a more recent version of Furnace!"); addWarning("this module was created with a more recent version of Furnace!"); } if (ds.version<37) { // compat flags not stored back then ds.limitSlides=true; ds.linearPitch=1; ds.loopModality=0; } if (ds.version<43) { ds.properNoiseLayout=false; ds.waveDutyIsVol=false; } if (ds.version<45) { ds.resetMacroOnPorta=true; ds.legacyVolumeSlides=true; ds.compatibleArpeggio=true; ds.noteOffResetsSlides=true; ds.targetResetsSlides=true; } if (ds.version<46) { ds.arpNonPorta=true; ds.algMacroBehavior=true; } else { ds.arpNonPorta=false; ds.algMacroBehavior=false; } if (ds.version<49) { ds.brokenShortcutSlides=true; } if (ds.version<50) { ds.ignoreDuplicateSlides=false; } if (ds.version<62) { ds.stopPortaOnNoteOff=true; } if (ds.version<64) { ds.brokenDACMode=false; } if (ds.version<65) { ds.oneTickCut=false; } if (ds.version<66) { ds.newInsTriggersInPorta=false; } if (ds.version<69) { ds.arp0Reset=false; } if (ds.version<71) { ds.noSlidesOnFirstTick=false; ds.rowResetsArpPos=false; ds.ignoreJumpAtEnd=true; } if (ds.version<72) { ds.buggyPortaAfterSlide=true; ds.gbInsAffectsEnvelope=false; } if (ds.version<78) { ds.sharedExtStat=false; } if (ds.version<83) { ds.ignoreDACModeOutsideIntendedChannel=true; ds.e1e2AlsoTakePriority=false; } if (ds.version<84) { ds.newSegaPCM=false; } if (ds.version<85) { ds.fbPortaPause=true; } if (ds.version<86) { ds.snDutyReset=true; } if (ds.version<90) { ds.pitchMacroIsLinear=false; } if (ds.version<97) { ds.oldOctaveBoundary=true; } if (ds.version<97) { // actually should be 98 but yky uses this feature ahead of time ds.noOPN2Vol=true; } if (ds.version<99) { ds.newVolumeScaling=false; ds.volMacroLinger=false; ds.brokenOutVol=true; } if (ds.version<100) { ds.e1e2StopOnSameNote=false; } if (ds.version<101) { ds.brokenPortaArp=true; } if (ds.version<108) { ds.snNoLowPeriods=true; } if (ds.version<110) { ds.delayBehavior=1; } if (ds.version<113) { ds.jumpTreatment=1; } if (ds.version<115) { ds.autoSystem=false; } if (ds.version<117) { ds.disableSampleMacro=true; } if (ds.version<121) { ds.brokenOutVol2=false; } if (ds.version<130) { ds.oldArpStrategy=true; } if (ds.version<138) { ds.brokenPortaLegato=true; } if (ds.version<155) { ds.brokenFMOff=true; } if (ds.version<168) { ds.preNoteNoEffect=true; } if (ds.version<183) { ds.oldDPCM=true; } if (ds.version<184) { ds.resetArpPhaseOnNewNote=false; } if (ds.version<188) { ds.ceilVolumeScaling=false; } if (ds.version<191) { ds.oldAlwaysSetVolume=true; } ds.isDMF=false; reader.readS(); // reserved int infoSeek=reader.readI(); if (!reader.seek(infoSeek,SEEK_SET)) { logE("couldn't seek to info header at %d!",infoSeek); lastError="couldn't seek to info header!"; delete[] file; return false; } // read header reader.read(magic,4); if (strcmp(magic,"INFO")!=0) { logE("invalid info header!"); lastError="invalid info header!"; delete[] file; return false; } reader.readI(); subSong->timeBase=reader.readC(); subSong->speeds.len=2; subSong->speeds.val[0]=reader.readC(); subSong->speeds.val[1]=reader.readC(); subSong->arpLen=reader.readC(); subSong->hz=reader.readF(); subSong->patLen=reader.readS(); subSong->ordersLen=reader.readS(); subSong->hilightA=reader.readC(); subSong->hilightB=reader.readC(); ds.insLen=reader.readS(); ds.waveLen=reader.readS(); ds.sampleLen=reader.readS(); int numberOfPats=reader.readI(); if (subSong->patLen<0) { logE("pattern length is negative!"); lastError="pattern lengrh is negative!"; delete[] file; return false; } if (subSong->patLen>DIV_MAX_ROWS) { logE("pattern length is too large!"); lastError="pattern length is too large!"; delete[] file; return false; } if (subSong->ordersLen<0) { logE("song length is negative!"); lastError="song length is negative!"; delete[] file; return false; } if (subSong->ordersLen>DIV_MAX_PATTERNS) { logE("song is too long!"); lastError="song is too long!"; delete[] file; return false; } if (ds.insLen<0 || ds.insLen>256) { logE("invalid instrument count!"); lastError="invalid instrument count!"; delete[] file; return false; } if (ds.waveLen<0 || ds.waveLen>256) { logE("invalid wavetable count!"); lastError="invalid wavetable count!"; delete[] file; return false; } if (ds.sampleLen<0 || ds.sampleLen>256) { logE("invalid sample count!"); lastError="invalid sample count!"; delete[] file; return false; } if (numberOfPats<0) { logE("invalid pattern count!"); lastError="invalid pattern count!"; delete[] file; return false; } logD("systems:"); ds.systemLen=0; for (int i=0; iDIV_MAX_CHANS) { tchans=DIV_MAX_CHANS; logW("too many channels!"); } logV("system len: %d",ds.systemLen); if (ds.systemLen<1) { logE("zero chips!"); lastError="zero chips!"; delete[] file; return false; } // system volume for (int i=0; ii; j--) { ds.system[j]=ds.system[j-1]; ds.systemVol[j]=ds.systemVol[j-1]; ds.systemPan[j]=ds.systemPan[j-1]; } if (++ds.systemLen>DIV_MAX_CHIPS) ds.systemLen=DIV_MAX_CHIPS; if (ds.system[i]==DIV_SYSTEM_GENESIS) { ds.system[i]=DIV_SYSTEM_YM2612; if (i<31) { ds.system[i+1]=DIV_SYSTEM_SMS; ds.systemVol[i+1]=ds.systemVol[i]*0.375f; } } if (ds.system[i]==DIV_SYSTEM_GENESIS_EXT) { ds.system[i]=DIV_SYSTEM_YM2612_EXT; if (i<31) { ds.system[i+1]=DIV_SYSTEM_SMS; ds.systemVol[i+1]=ds.systemVol[i]*0.375f; } } if (ds.system[i]==DIV_SYSTEM_ARCADE) { ds.system[i]=DIV_SYSTEM_YM2151; if (i<31) { ds.system[i+1]=DIV_SYSTEM_SEGAPCM_COMPAT; } } i++; } } ds.name=reader.readString(); ds.author=reader.readString(); logI("%s by %s",ds.name.c_str(),ds.author.c_str()); if (ds.version>=33) { ds.tuning=reader.readF(); } else { reader.readI(); } // compatibility flags if (ds.version>=37) { ds.limitSlides=reader.readC(); ds.linearPitch=reader.readC(); ds.loopModality=reader.readC(); if (ds.version>=43) { ds.properNoiseLayout=reader.readC(); } else { reader.readC(); } if (ds.version>=43) { ds.waveDutyIsVol=reader.readC(); } else { reader.readC(); } if (ds.version>=45) { ds.resetMacroOnPorta=reader.readC(); } else { reader.readC(); } if (ds.version>=45) { ds.legacyVolumeSlides=reader.readC(); } else { reader.readC(); } if (ds.version>=45) { ds.compatibleArpeggio=reader.readC(); } else { reader.readC(); } if (ds.version>=45) { ds.noteOffResetsSlides=reader.readC(); } else { reader.readC(); } if (ds.version>=45) { ds.targetResetsSlides=reader.readC(); } else { reader.readC(); } if (ds.version>=47) { ds.arpNonPorta=reader.readC(); } else { reader.readC(); } if (ds.version>=47) { ds.algMacroBehavior=reader.readC(); } else { reader.readC(); } if (ds.version>=49) { ds.brokenShortcutSlides=reader.readC(); } else { reader.readC(); } if (ds.version>=50) { ds.ignoreDuplicateSlides=reader.readC(); } else { reader.readC(); } if (ds.version>=62) { ds.stopPortaOnNoteOff=reader.readC(); ds.continuousVibrato=reader.readC(); } else { reader.readC(); reader.readC(); } if (ds.version>=64) { ds.brokenDACMode=reader.readC(); } else { reader.readC(); } if (ds.version>=65) { ds.oneTickCut=reader.readC(); } else { reader.readC(); } if (ds.version>=66) { ds.newInsTriggersInPorta=reader.readC(); } else { reader.readC(); } if (ds.version>=69) { ds.arp0Reset=reader.readC(); } else { reader.readC(); } } else { for (int i=0; i<20; i++) reader.readC(); } // pointers for (int i=0; iordersLen); for (int i=0; iordersLen; j++) { subSong->orders.ord[i][j]=reader.readC(); } } for (int i=0; ipat[i].effectCols=reader.readC(); if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>DIV_MAX_EFFECTS) { logE("channel %d has zero or too many effect columns! (%d)",i,subSong->pat[i].effectCols); lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,subSong->pat[i].effectCols); delete[] file; return false; } } if (ds.version>=39) { for (int i=0; ichanShow[i]=reader.readC(); subSong->chanShowChanOsc[i]=subSong->chanShow[i]; } else { unsigned char tempchar=reader.readC(); subSong->chanShow[i]=tempchar&1; subSong->chanShowChanOsc[i]=(tempchar&2); } } for (int i=0; ichanCollapse[i]=reader.readC(); } if (ds.version<92) { for (int i=0; ichanCollapse[i]>0) subSong->chanCollapse[i]=3; } } for (int i=0; ichanName[i]=reader.readString(); } for (int i=0; ichanShortName[i]=reader.readString(); } ds.notes=reader.readString(); } if (ds.version>=59) { ds.masterVol=reader.readF(); } else { ds.masterVol=2.0f; } if (ds.version>=70) { // extended compat flags ds.brokenSpeedSel=reader.readC(); if (ds.version>=71) { ds.noSlidesOnFirstTick=reader.readC(); ds.rowResetsArpPos=reader.readC(); ds.ignoreJumpAtEnd=reader.readC(); } else { reader.readC(); reader.readC(); reader.readC(); } if (ds.version>=72) { ds.buggyPortaAfterSlide=reader.readC(); ds.gbInsAffectsEnvelope=reader.readC(); } else { reader.readC(); reader.readC(); } if (ds.version>=78) { ds.sharedExtStat=reader.readC(); } else { reader.readC(); } if (ds.version>=83) { ds.ignoreDACModeOutsideIntendedChannel=reader.readC(); ds.e1e2AlsoTakePriority=reader.readC(); } else { reader.readC(); reader.readC(); } if (ds.version>=84) { ds.newSegaPCM=reader.readC(); } else { reader.readC(); } if (ds.version>=85) { ds.fbPortaPause=reader.readC(); } else { reader.readC(); } if (ds.version>=86) { ds.snDutyReset=reader.readC(); } else { reader.readC(); } if (ds.version>=90) { ds.pitchMacroIsLinear=reader.readC(); } else { reader.readC(); } if (ds.version>=94) { ds.pitchSlideSpeed=reader.readC(); } else { reader.readC(); } if (ds.version>=97) { ds.oldOctaveBoundary=reader.readC(); } else { reader.readC(); } if (ds.version>=98) { ds.noOPN2Vol=reader.readC(); } else { reader.readC(); } if (ds.version>=99) { ds.newVolumeScaling=reader.readC(); ds.volMacroLinger=reader.readC(); ds.brokenOutVol=reader.readC(); } else { reader.readC(); reader.readC(); reader.readC(); } if (ds.version>=100) { ds.e1e2StopOnSameNote=reader.readC(); } else { reader.readC(); } if (ds.version>=101) { ds.brokenPortaArp=reader.readC(); } else { reader.readC(); } if (ds.version>=108) { ds.snNoLowPeriods=reader.readC(); } else { reader.readC(); } if (ds.version>=110) { ds.delayBehavior=reader.readC(); } else { reader.readC(); } if (ds.version>=113) { ds.jumpTreatment=reader.readC(); } else { reader.readC(); } if (ds.version>=115) { ds.autoSystem=reader.readC(); } else { reader.readC(); } if (ds.version>=117) { ds.disableSampleMacro=reader.readC(); } else { reader.readC(); } if (ds.version>=121) { ds.brokenOutVol2=reader.readC(); } else { reader.readC(); } if (ds.version>=130) { ds.oldArpStrategy=reader.readC(); } else { reader.readC(); } } // first song virtual tempo if (ds.version>=96) { subSong->virtualTempoN=reader.readS(); subSong->virtualTempoD=reader.readS(); } else { reader.readI(); } // subsongs if (ds.version>=95) { subSong->name=reader.readString(); subSong->notes=reader.readString(); numberOfSubSongs=(unsigned char)reader.readC(); reader.readC(); // reserved reader.readC(); reader.readC(); // pointers for (int i=0; i=103) { ds.systemName=reader.readString(); ds.category=reader.readString(); ds.nameJ=reader.readString(); ds.authorJ=reader.readString(); ds.systemNameJ=reader.readString(); ds.categoryJ=reader.readString(); } else { ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); ds.autoSystem=true; } // system output config if (ds.version>=135) { for (int i=0; i0) ds.patchbay.reserve(conns); for (unsigned int i=0; i=136) ds.patchbayAuto=reader.readC(); if (ds.version>=138) { ds.brokenPortaLegato=reader.readC(); if (ds.version>=155) { ds.brokenFMOff=reader.readC(); } else { reader.readC(); } if (ds.version>=168) { ds.preNoteNoEffect=reader.readC(); } else { reader.readC(); } if (ds.version>=183) { ds.oldDPCM=reader.readC(); } else { reader.readC(); } if (ds.version>=184) { ds.resetArpPhaseOnNewNote=reader.readC(); } else { reader.readC(); } if (ds.version>=188) { ds.ceilVolumeScaling=reader.readC(); } else { reader.readC(); } if (ds.version>=191) { ds.oldAlwaysSetVolume=reader.readC(); } else { reader.readC(); } for (int i=0; i<1; i++) { reader.readC(); } } if (ds.version>=139) { subSong->speeds.len=reader.readC(); for (int i=0; i<16; i++) { subSong->speeds.val[i]=reader.readC(); } // grooves unsigned char grooveCount=reader.readC(); ds.grooves.reserve(grooveCount); for (int i=0; i=156) { assetDirPtr[0]=reader.readI(); assetDirPtr[1]=reader.readI(); assetDirPtr[2]=reader.readI(); } // read system flags if (ds.version>=119) { logD("reading chip flags..."); for (int i=0; i=156) { logD("reading asset directories..."); if (!reader.seek(assetDirPtr[0],SEEK_SET)) { logE("couldn't seek to ins dir!"); lastError=fmt::sprintf("couldn't read instrument directory"); ds.unload(); delete[] file; return false; } if (readAssetDirData(reader,ds.insDir)!=DIV_DATA_SUCCESS) { lastError="invalid instrument directory data!"; ds.unload(); delete[] file; return false; } if (!reader.seek(assetDirPtr[1],SEEK_SET)) { logE("couldn't seek to wave dir!"); lastError=fmt::sprintf("couldn't read wavetable directory"); ds.unload(); delete[] file; return false; } if (readAssetDirData(reader,ds.waveDir)!=DIV_DATA_SUCCESS) { lastError="invalid wavetable directory data!"; ds.unload(); delete[] file; return false; } if (!reader.seek(assetDirPtr[2],SEEK_SET)) { logE("couldn't seek to sample dir!"); lastError=fmt::sprintf("couldn't read sample directory"); ds.unload(); delete[] file; return false; } if (readAssetDirData(reader,ds.sampleDir)!=DIV_DATA_SUCCESS) { lastError="invalid sample directory data!"; ds.unload(); delete[] file; return false; } } // read subsongs if (ds.version>=95) { ds.subsong.reserve(numberOfSubSongs); for (int i=0; itimeBase=reader.readC(); subSong->speeds.len=2; subSong->speeds.val[0]=reader.readC(); subSong->speeds.val[1]=reader.readC(); subSong->arpLen=reader.readC(); subSong->hz=reader.readF(); subSong->patLen=reader.readS(); subSong->ordersLen=reader.readS(); subSong->hilightA=reader.readC(); subSong->hilightB=reader.readC(); if (ds.version>=96) { subSong->virtualTempoN=reader.readS(); subSong->virtualTempoD=reader.readS(); } else { reader.readI(); } subSong->name=reader.readString(); subSong->notes=reader.readString(); logD("reading orders of subsong %d (%d)...",i+1,subSong->ordersLen); for (int j=0; jordersLen; k++) { subSong->orders.ord[j][k]=reader.readC(); } } for (int i=0; ipat[i].effectCols=reader.readC(); } for (int i=0; ichanShow[i]=reader.readC(); subSong->chanShowChanOsc[i]=subSong->chanShow[i]; } else { unsigned char tempchar=reader.readC(); subSong->chanShow[i]=tempchar&1; subSong->chanShowChanOsc[i]=tempchar&2; } } for (int i=0; ichanCollapse[i]=reader.readC(); } for (int i=0; ichanName[i]=reader.readString(); } for (int i=0; ichanShortName[i]=reader.readString(); } if (ds.version>=139) { subSong->speeds.len=reader.readC(); for (int i=0; i<16; i++) { subSong->speeds.val[i]=reader.readC(); } } } } // read instruments ds.ins.reserve(ds.insLen); for (int i=0; ireadInsData(reader,ds.version)!=DIV_DATA_SUCCESS) { lastError="invalid instrument header/data!"; ds.unload(); delete ins; delete[] file; return false; } ds.ins.push_back(ins); } // read wavetables ds.wave.reserve(ds.waveLen); for (int i=0; ireadWaveData(reader,ds.version)!=DIV_DATA_SUCCESS) { lastError="invalid wavetable header/data!"; ds.unload(); delete wave; delete[] file; return false; } ds.wave.push_back(wave); } // read samples ds.sample.reserve(ds.sampleLen); for (int i=0; ireadSampleData(reader,ds.version)!=DIV_DATA_SUCCESS) { lastError="invalid sample header/data!"; ds.unload(); delete sample; delete[] file; return false; } ds.sample.push_back(sample); } // read patterns for (unsigned int i: patPtr) { bool isNewFormat=false; if (!reader.seek(i,SEEK_SET)) { logE("couldn't seek to pattern in %x!",i); lastError=fmt::sprintf("couldn't seek to pattern in %x!",i); ds.unload(); delete[] file; return false; } reader.read(magic,4); logD("reading pattern in %x...",i); if (strcmp(magic,"PATR")!=0) { if (strcmp(magic,"PATN")!=0 || ds.version<157) { logE("%x: invalid pattern header!",i); lastError="invalid pattern header!"; ds.unload(); delete[] file; return false; } else { isNewFormat=true; } } reader.readI(); if (isNewFormat) { int subs=(unsigned char)reader.readC(); int chan=(unsigned char)reader.readC(); int index=reader.readS(); logD("- %d, %d, %d (new)",subs,chan,index); if (chan<0 || chan>=tchans) { logE("pattern channel out of range!",i); lastError="pattern channel out of range!"; ds.unload(); delete[] file; return false; } if (index<0 || index>(DIV_MAX_PATTERNS-1)) { logE("pattern index out of range!",i); lastError="pattern index out of range!"; ds.unload(); delete[] file; return false; } if (subs<0 || subs>=(int)ds.subsong.size()) { logE("pattern subsong out of range!",i); lastError="pattern subsong out of range!"; ds.unload(); delete[] file; return false; } DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true); pat->name=reader.readString(); // read new pattern for (int j=0; jpatLen; j++) { unsigned char mask=reader.readC(); unsigned short effectMask=0; if (mask==0xff) break; if (mask&128) { j+=(mask&127)+1; continue; } if (mask&32) { effectMask|=(unsigned char)reader.readC(); } if (mask&64) { effectMask|=((unsigned short)reader.readC()&0xff)<<8; } if (mask&8) effectMask|=1; if (mask&16) effectMask|=2; if (mask&1) { // note unsigned char note=reader.readC(); if (note==180) { pat->data[j][0]=100; pat->data[j][1]=0; } else if (note==181) { pat->data[j][0]=101; pat->data[j][1]=0; } else if (note==182) { pat->data[j][0]=102; pat->data[j][1]=0; } else if (note<180) { pat->data[j][0]=newFormatNotes[note]; pat->data[j][1]=newFormatOctaves[note]; } else { pat->data[j][0]=0; pat->data[j][1]=0; } } if (mask&2) { // instrument pat->data[j][2]=(unsigned char)reader.readC(); } if (mask&4) { // volume pat->data[j][3]=(unsigned char)reader.readC(); } for (unsigned char k=0; k<16; k++) { if (effectMask&(1<data[j][4+k]=(unsigned char)reader.readC(); } } } } else { int chan=reader.readS(); int index=reader.readS(); int subs=0; if (ds.version>=95) { subs=reader.readS(); } else { reader.readS(); } reader.readS(); logD("- %d, %d, %d (old)",subs,chan,index); if (chan<0 || chan>=tchans) { logE("pattern channel out of range!",i); lastError="pattern channel out of range!"; ds.unload(); delete[] file; return false; } if (index<0 || index>(DIV_MAX_PATTERNS-1)) { logE("pattern index out of range!",i); lastError="pattern index out of range!"; ds.unload(); delete[] file; return false; } if (subs<0 || subs>=(int)ds.subsong.size()) { logE("pattern subsong out of range!",i); lastError="pattern subsong out of range!"; ds.unload(); delete[] file; return false; } DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true); for (int j=0; jpatLen; j++) { pat->data[j][0]=reader.readS(); pat->data[j][1]=reader.readS(); pat->data[j][2]=reader.readS(); pat->data[j][3]=reader.readS(); for (int k=0; kpat[chan].effectCols; k++) { pat->data[j][4+(k<<1)]=reader.readS(); pat->data[j][5+(k<<1)]=reader.readS(); } if (pat->data[j][0]==0 && pat->data[j][1]!=0) { logD("what? %d:%d:%d note %d octave %d",chan,i,j,pat->data[j][0],pat->data[j][1]); pat->data[j][0]=12; pat->data[j][1]--; } } if (ds.version>=51) { pat->name=reader.readString(); } } } if (reader.tell()opnCount) { for (DivInstrument* i: ds.ins) { if (i->type==DIV_INS_FM) i->type=DIV_INS_OPM; } } if (nesCount>snCount) { for (DivInstrument* i: ds.ins) { if (i->type==DIV_INS_STD) i->type=DIV_INS_NES; } } } // ExtCh compat flag for (int i=0; i subSongPtr; std::vector sysFlagsPtr; std::vector insPtr; std::vector wavePtr; std::vector samplePtr; std::vector patPtr; int assetDirPtr[3]; size_t ptrSeek, subSongPtrSeek, sysFlagsPtrSeek, blockStartSeek, blockEndSeek, assetDirPtrSeek; size_t subSongIndex=0; DivSubSong* subSong=song.subsong[subSongIndex]; warnings=""; // fail if values are out of range /* if (subSong->ordersLen>DIV_MAX_PATTERNS) { logE("maximum song length is %d!",DIV_MAX_PATTERNS); lastError=fmt::sprintf("maximum song length is %d",DIV_MAX_PATTERNS); return NULL; } if (subSong->patLen>DIV_MAX_ROWS) { logE("maximum pattern length is %d!",DIV_MAX_ROWS); lastError=fmt::sprintf("maximum pattern length is %d",DIV_MAX_ROWS); return NULL; } */ if (song.ins.size()>256) { logE("maximum number of instruments is 256!"); lastError="maximum number of instruments is 256"; saveLock.unlock(); return NULL; } if (song.wave.size()>256) { logE("maximum number of wavetables is 256!"); lastError="maximum number of wavetables is 256"; saveLock.unlock(); return NULL; } if (song.sample.size()>256) { logE("maximum number of samples is 256!"); lastError="maximum number of samples is 256"; saveLock.unlock(); return NULL; } if (!notPrimary) { song.isDMF=false; song.version=DIV_ENGINE_VERSION; } SafeWriter* w=new SafeWriter; w->init(); /// HEADER // write magic w->write(DIV_FUR_MAGIC,16); // write version w->writeS(DIV_ENGINE_VERSION); // reserved w->writeS(0); // song info pointer w->writeI(32); // reserved w->writeI(0); w->writeI(0); // high short is channel // low short is pattern number std::vector patsToWrite; if (getConfInt("saveUnusedPatterns",0)==1) { for (int i=0; ipat[i].data[k]==NULL) continue; patsToWrite.push_back(PatToWrite(j,i,k)); } } } } else { bool alreadyAdded[DIV_MAX_PATTERNS]; for (int i=0; iordersLen; k++) { if (alreadyAdded[subs->orders.ord[i][k]]) continue; patsToWrite.push_back(PatToWrite(j,i,subs->orders.ord[i][k])); alreadyAdded[subs->orders.ord[i][k]]=true; } } } } /// SONG INFO w->write("INFO",4); blockStartSeek=w->tell(); w->writeI(0); w->writeC(subSong->timeBase); // these are for compatibility w->writeC(subSong->speeds.val[0]); w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]); w->writeC(subSong->arpLen); w->writeF(subSong->hz); w->writeS(subSong->patLen); w->writeS(subSong->ordersLen); w->writeC(subSong->hilightA); w->writeC(subSong->hilightB); w->writeS(song.insLen); w->writeS(song.waveLen); w->writeS(song.sampleLen); w->writeI(patsToWrite.size()); for (int i=0; i=song.systemLen) { w->writeC(0); } else { w->writeC(systemToFileFur(song.system[i])); } } for (int i=0; iwriteC(song.systemVol[i]*64.0f); } for (int i=0; iwriteC(song.systemPan[i]*127.0f); } // chip flags (we'll seek here later) sysFlagsPtrSeek=w->tell(); for (int i=0; iwriteI(0); } // song name w->writeString(song.name,false); // song author w->writeString(song.author,false); w->writeF(song.tuning); // compatibility flags w->writeC(song.limitSlides); w->writeC(song.linearPitch); w->writeC(song.loopModality); w->writeC(song.properNoiseLayout); w->writeC(song.waveDutyIsVol); w->writeC(song.resetMacroOnPorta); w->writeC(song.legacyVolumeSlides); w->writeC(song.compatibleArpeggio); w->writeC(song.noteOffResetsSlides); w->writeC(song.targetResetsSlides); w->writeC(song.arpNonPorta); w->writeC(song.algMacroBehavior); w->writeC(song.brokenShortcutSlides); w->writeC(song.ignoreDuplicateSlides); w->writeC(song.stopPortaOnNoteOff); w->writeC(song.continuousVibrato); w->writeC(song.brokenDACMode); w->writeC(song.oneTickCut); w->writeC(song.newInsTriggersInPorta); w->writeC(song.arp0Reset); ptrSeek=w->tell(); // instrument pointers (we'll seek here later) for (int i=0; iwriteI(0); } // wavetable pointers (we'll seek here later) for (int i=0; iwriteI(0); } // sample pointers (we'll seek here later) for (int i=0; iwriteI(0); } // pattern pointers (we'll seek here later) for (size_t i=0; iwriteI(0); } for (int i=0; iordersLen; j++) { w->writeC(subSong->orders.ord[i][j]); } } for (int i=0; iwriteC(subSong->pat[i].effectCols); } for (int i=0; iwriteC( (subSong->chanShow[i]?1:0)| (subSong->chanShowChanOsc[i]?2:0) ); } for (int i=0; iwriteC(subSong->chanCollapse[i]); } for (int i=0; iwriteString(subSong->chanName[i],false); } for (int i=0; iwriteString(subSong->chanShortName[i],false); } w->writeString(song.notes,false); w->writeF(song.masterVol); // extended compat flags w->writeC(song.brokenSpeedSel); w->writeC(song.noSlidesOnFirstTick); w->writeC(song.rowResetsArpPos); w->writeC(song.ignoreJumpAtEnd); w->writeC(song.buggyPortaAfterSlide); w->writeC(song.gbInsAffectsEnvelope); w->writeC(song.sharedExtStat); w->writeC(song.ignoreDACModeOutsideIntendedChannel); w->writeC(song.e1e2AlsoTakePriority); w->writeC(song.newSegaPCM); w->writeC(song.fbPortaPause); w->writeC(song.snDutyReset); w->writeC(song.pitchMacroIsLinear); w->writeC(song.pitchSlideSpeed); w->writeC(song.oldOctaveBoundary); w->writeC(song.noOPN2Vol); w->writeC(song.newVolumeScaling); w->writeC(song.volMacroLinger); w->writeC(song.brokenOutVol); w->writeC(song.e1e2StopOnSameNote); w->writeC(song.brokenPortaArp); w->writeC(song.snNoLowPeriods); w->writeC(song.delayBehavior); w->writeC(song.jumpTreatment); w->writeC(song.autoSystem); w->writeC(song.disableSampleMacro); w->writeC(song.brokenOutVol2); w->writeC(song.oldArpStrategy); // first subsong virtual tempo w->writeS(subSong->virtualTempoN); w->writeS(subSong->virtualTempoD); // subsong list w->writeString(subSong->name,false); w->writeString(subSong->notes,false); w->writeC((unsigned char)(song.subsong.size()-1)); w->writeC(0); // reserved w->writeC(0); w->writeC(0); subSongPtrSeek=w->tell(); // subsong pointers (we'll seek here later) for (size_t i=0; i<(song.subsong.size()-1); i++) { w->writeI(0); } // additional metadata w->writeString(song.systemName,false); w->writeString(song.category,false); w->writeString(song.nameJ,false); w->writeString(song.authorJ,false); w->writeString(song.systemNameJ,false); w->writeString(song.categoryJ,false); // system output config for (int i=0; iwriteF(song.systemVol[i]); w->writeF(song.systemPan[i]); w->writeF(song.systemPanFR[i]); } w->writeI(song.patchbay.size()); for (unsigned int i: song.patchbay) { w->writeI(i); } w->writeC(song.patchbayAuto); // even more compat flags w->writeC(song.brokenPortaLegato); w->writeC(song.brokenFMOff); w->writeC(song.preNoteNoEffect); w->writeC(song.oldDPCM); w->writeC(song.resetArpPhaseOnNewNote); w->writeC(song.ceilVolumeScaling); w->writeC(song.oldAlwaysSetVolume); for (int i=0; i<1; i++) { w->writeC(0); } // speeds of first song w->writeC(subSong->speeds.len); for (int i=0; i<16; i++) { w->writeC(subSong->speeds.val[i]); } // groove list w->writeC((unsigned char)song.grooves.size()); for (const DivGroovePattern& i: song.grooves) { w->writeC(i.len); for (int j=0; j<16; j++) { w->writeC(i.val[j]); } } // asset dir pointers (we'll seek here later) assetDirPtrSeek=w->tell(); w->writeI(0); w->writeI(0); w->writeI(0); blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); w->writeI(blockEndSeek-blockStartSeek-4); w->seek(0,SEEK_END); /// SUBSONGS subSongPtr.reserve(song.subsong.size() - 1); for (subSongIndex=1; subSongIndextell()); w->write("SONG",4); blockStartSeek=w->tell(); w->writeI(0); w->writeC(subSong->timeBase); w->writeC(subSong->speeds.val[0]); w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]); w->writeC(subSong->arpLen); w->writeF(subSong->hz); w->writeS(subSong->patLen); w->writeS(subSong->ordersLen); w->writeC(subSong->hilightA); w->writeC(subSong->hilightB); w->writeS(subSong->virtualTempoN); w->writeS(subSong->virtualTempoD); w->writeString(subSong->name,false); w->writeString(subSong->notes,false); for (int i=0; iordersLen; j++) { w->writeC(subSong->orders.ord[i][j]); } } for (int i=0; iwriteC(subSong->pat[i].effectCols); } for (int i=0; iwriteC( (subSong->chanShow[i]?1:0)| (subSong->chanShowChanOsc[i]?2:0) ); } for (int i=0; iwriteC(subSong->chanCollapse[i]); } for (int i=0; iwriteString(subSong->chanName[i],false); } for (int i=0; iwriteString(subSong->chanShortName[i],false); } // speeds w->writeC(subSong->speeds.len); for (int i=0; i<16; i++) { w->writeC(subSong->speeds.val[i]); } blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); w->writeI(blockEndSeek-blockStartSeek-4); w->seek(0,SEEK_END); } /// CHIP FLAGS sysFlagsPtr.reserve(song.systemLen); for (int i=0; itell()); w->write("FLAG",4); blockStartSeek=w->tell(); w->writeI(0); w->writeString(data,false); blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); w->writeI(blockEndSeek-blockStartSeek-4); w->seek(0,SEEK_END); } /// ASSET DIRECTORIES assetDirPtr[0]=w->tell(); putAssetDirData(w,song.insDir); assetDirPtr[1]=w->tell(); putAssetDirData(w,song.waveDir); assetDirPtr[2]=w->tell(); putAssetDirData(w,song.sampleDir); /// INSTRUMENT insPtr.reserve(song.insLen); for (int i=0; itell()); ins->putInsData2(w,false); } /// WAVETABLE wavePtr.reserve(song.waveLen); for (int i=0; itell()); wave->putWaveData(w); } /// SAMPLE samplePtr.reserve(song.sampleLen); for (int i=0; itell()); sample->putSampleData(w); } /// PATTERN patPtr.reserve(patsToWrite.size()); for (PatToWrite& i: patsToWrite) { DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false); patPtr.push_back(w->tell()); if (newPatternFormat) { w->write("PATN",4); blockStartSeek=w->tell(); w->writeI(0); w->writeC(i.subsong); w->writeC(i.chan); w->writeS(i.pat); w->writeString(pat->name,false); unsigned char emptyRows=0; for (int j=0; jpatLen; j++) { unsigned char mask=0; unsigned char finalNote=255; unsigned short effectMask=0; if (pat->data[j][0]==100) { finalNote=180; } else if (pat->data[j][0]==101) { // note release finalNote=181; } else if (pat->data[j][0]==102) { // macro release finalNote=182; } else if (pat->data[j][1]==0 && pat->data[j][0]==0) { finalNote=255; } else { int seek=(pat->data[j][0]+(signed char)pat->data[j][1]*12)+60; if (seek<0 || seek>=180) { finalNote=255; } else { finalNote=seek; } } if (finalNote!=255) mask|=1; // note if (pat->data[j][2]!=-1) mask|=2; // instrument if (pat->data[j][3]!=-1) mask|=4; // volume for (int k=0; kpat[i.chan].effectCols*2; k+=2) { if (k==0) { if (pat->data[j][4+k]!=-1) mask|=8; if (pat->data[j][5+k]!=-1) mask|=16; } else if (k<8) { if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=32; } else { if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=64; } if (pat->data[j][4+k]!=-1) effectMask|=(1<data[j][5+k]!=-1) effectMask|=(2<127) { w->writeC(128|(emptyRows-2)); emptyRows=0; } } else { if (emptyRows>1) { w->writeC(128|(emptyRows-2)); emptyRows=0; } else if (emptyRows) { w->writeC(0); emptyRows=0; } w->writeC(mask); if (mask&32) w->writeC(effectMask&0xff); if (mask&64) w->writeC((effectMask>>8)&0xff); if (mask&1) w->writeC(finalNote); if (mask&2) w->writeC(pat->data[j][2]); if (mask&4) w->writeC(pat->data[j][3]); if (mask&8) w->writeC(pat->data[j][4]); if (mask&16) w->writeC(pat->data[j][5]); if (mask&32) { if (effectMask&4) w->writeC(pat->data[j][6]); if (effectMask&8) w->writeC(pat->data[j][7]); if (effectMask&16) w->writeC(pat->data[j][8]); if (effectMask&32) w->writeC(pat->data[j][9]); if (effectMask&64) w->writeC(pat->data[j][10]); if (effectMask&128) w->writeC(pat->data[j][11]); } if (mask&64) { if (effectMask&256) w->writeC(pat->data[j][12]); if (effectMask&512) w->writeC(pat->data[j][13]); if (effectMask&1024) w->writeC(pat->data[j][14]); if (effectMask&2048) w->writeC(pat->data[j][15]); if (effectMask&4096) w->writeC(pat->data[j][16]); if (effectMask&8192) w->writeC(pat->data[j][17]); if (effectMask&16384) w->writeC(pat->data[j][18]); if (effectMask&32768) w->writeC(pat->data[j][19]); } } } // stop w->writeC(0xff); } else { w->write("PATR",4); blockStartSeek=w->tell(); w->writeI(0); w->writeS(i.chan); w->writeS(i.pat); w->writeS(i.subsong); w->writeS(0); // reserved for (int j=0; jpatLen; j++) { w->writeS(pat->data[j][0]); // note w->writeS(pat->data[j][1]); // octave w->writeS(pat->data[j][2]); // instrument w->writeS(pat->data[j][3]); // volume #ifdef TA_BIG_ENDIAN for (int k=0; kpat[i.chan].effectCols*2; k++) { w->writeS(pat->data[j][4+k]); } #else w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects #endif } w->writeString(pat->name,false); } blockEndSeek=w->tell(); w->seek(blockStartSeek,SEEK_SET); w->writeI(blockEndSeek-blockStartSeek-4); w->seek(0,SEEK_END); } /// POINTERS w->seek(ptrSeek,SEEK_SET); for (int i=0; iwriteI(insPtr[i]); } // wavetable pointers for (int i=0; iwriteI(wavePtr[i]); } // sample pointers for (int i=0; iwriteI(samplePtr[i]); } // pattern pointers for (int i: patPtr) { w->writeI(i); } // subsong pointers w->seek(subSongPtrSeek,SEEK_SET); for (size_t i=0; i<(song.subsong.size()-1); i++) { w->writeI(subSongPtr[i]); } // flag pointers w->seek(sysFlagsPtrSeek,SEEK_SET); for (size_t i=0; iwriteI(sysFlagsPtr[i]); } // asset dir pointers w->seek(assetDirPtrSeek,SEEK_SET); for (size_t i=0; i<3; i++) { w->writeI(assetDirPtr[i]); } saveLock.unlock(); return w; }