/** * 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" // known version numbers: // - 27: v1.1.7 // - current format version // - adds sample start/end points // - 26: v1.1.3 // - changes height of FDS wave to 6-bit (it was 4-bit before) // - 25: v1.1 // - adds pattern names (in a rather odd way) // - v1.1.4 fixes these but breaks old songs (yeah) // - introduces SMS+OPLL system // - 24: v0.12/0.13/1.0 // - changes pattern length from char to int, probably to allow for size 256 // - 23: ??? // - what happened here? // - 20: v11.1 (?) // - E5xx effect range is now ±1 semitone // - 19: v11 // - introduces Arcade system // - changes to the FM instrument format due to YMU759 being dropped // - 18: v10 // - radically changes STD instrument for Game Boy // - 17: v9 // - changes C64 volIsCutoff flag from int to char for unknown reasons // - 16: v8 (?) // - introduces C64 system // - 15: v7 (?) // - 14: v6 (?) // - introduces NES system // - changes macro and wave values from char to int // - 13: v5.1 // - introduces PC Engine system in later version (how?) // - stores highlight in file // - 12: v5 (?) // - introduces Game Boy system // - introduces wavetables // - 11: ??? // - introduces Sega Master System // - custom Hz support // - instrument type (FM/STD) present // - prior to this version the instrument type depended on the system // - 10: ??? // - introduces multiple effect columns // - 9: v3.9 // - introduces Genesis system // - introduces system number // - patterns now stored in current known format // - 8: ??? // - only used in the Medivo YMU cover // - 7: ??? // - only present in a later version of First.dmf // - pattern format changes: empty field is 0xFF instead of 0x80 // - instrument now stored in pattern // - 5: BETA 3 // - adds arpeggio tick // - 4: BETA 2 // - possibly adds instrument number (stored in channel)? // - cannot confirm as I don't have any version 4 modules // - 3: BETA 1 // - possibly the first version that could save // - basic format, no system number, 16 instruments, one speed, YMU759-only // - patterns were stored in a different format (chars instead of shorts) and no instrument // - if somebody manages to find a version 2 or even 1 module, please tell me as it will be worth a lot static double samplePitches[11]={ 0.1666666666, 0.2, 0.25, 0.333333333, 0.5, 1, 2, 3, 4, 5, 6 }; bool DivEngine::loadDMF(unsigned char* file, size_t len) { SafeReader reader=SafeReader(file,len); warnings=""; try { DivSong ds; unsigned char historicColIns[DIV_MAX_CHANS]; for (int i=0; i0x1b) { logE("this version is not supported by Furnace yet!"); lastError="this version is not supported by Furnace yet"; delete[] file; return false; } unsigned char sys=0; ds.systemLen=1; if (ds.version<0x09) { // V E R S I O N -> 3 <- // AWESOME ds.system[0]=DIV_SYSTEM_YMU759; } else { sys=reader.readC(); ds.system[0]=systemFromFileDMF(sys); } if (ds.system[0]==DIV_SYSTEM_NULL) { logE("invalid system 0x%.2x!",sys); lastError="system not supported. running old version?"; delete[] file; return false; } if (ds.system[0]==DIV_SYSTEM_YMU759 && ds.version<0x10) { ds.vendor=reader.readString((unsigned char)reader.readC()); ds.carrier=reader.readString((unsigned char)reader.readC()); ds.category=reader.readString((unsigned char)reader.readC()); ds.name=reader.readString((unsigned char)reader.readC()); ds.author=reader.readString((unsigned char)reader.readC()); ds.writer=reader.readString((unsigned char)reader.readC()); ds.composer=reader.readString((unsigned char)reader.readC()); ds.arranger=reader.readString((unsigned char)reader.readC()); ds.copyright=reader.readString((unsigned char)reader.readC()); ds.manGroup=reader.readString((unsigned char)reader.readC()); ds.manInfo=reader.readString((unsigned char)reader.readC()); ds.createdDate=reader.readString((unsigned char)reader.readC()); ds.revisionDate=reader.readString((unsigned char)reader.readC()); logI("%s by %s",ds.name.c_str(),ds.author.c_str()); logI("has YMU-specific data:"); logI("- carrier: %s",ds.carrier.c_str()); logI("- category: %s",ds.category.c_str()); logI("- vendor: %s",ds.vendor.c_str()); logI("- writer: %s",ds.writer.c_str()); logI("- composer: %s",ds.composer.c_str()); logI("- arranger: %s",ds.arranger.c_str()); logI("- copyright: %s",ds.copyright.c_str()); logI("- management group: %s",ds.manGroup.c_str()); logI("- management info: %s",ds.manInfo.c_str()); logI("- created on: %s",ds.createdDate.c_str()); logI("- revision date: %s",ds.revisionDate.c_str()); } else { ds.name=reader.readString((unsigned char)reader.readC()); ds.author=reader.readString((unsigned char)reader.readC()); logI("%s by %s",ds.name.c_str(),ds.author.c_str()); } // compatibility flags if (!getConfInt("noDMFCompat",0)) { ds.limitSlides=true; ds.linearPitch=1; ds.loopModality=0; ds.properNoiseLayout=false; ds.waveDutyIsVol=false; // TODO: WHAT?! geodude.dmf fails when this is true // but isn't that how Defle behaves??? ds.resetMacroOnPorta=false; ds.legacyVolumeSlides=true; ds.compatibleArpeggio=true; ds.noteOffResetsSlides=true; ds.targetResetsSlides=true; ds.arpNonPorta=false; ds.algMacroBehavior=false; ds.brokenShortcutSlides=false; ds.ignoreDuplicateSlides=true; ds.brokenDACMode=true; ds.oneTickCut=false; ds.newInsTriggersInPorta=true; ds.arp0Reset=true; ds.brokenSpeedSel=true; ds.noSlidesOnFirstTick=false; ds.rowResetsArpPos=false; ds.ignoreJumpAtEnd=true; ds.buggyPortaAfterSlide=true; ds.gbInsAffectsEnvelope=true; ds.ignoreDACModeOutsideIntendedChannel=false; ds.e1e2AlsoTakePriority=true; ds.fbPortaPause=true; ds.snDutyReset=true; ds.oldOctaveBoundary=false; ds.noOPN2Vol=true; ds.newVolumeScaling=false; ds.volMacroLinger=false; ds.brokenOutVol=true; ds.brokenOutVol2=true; ds.e1e2StopOnSameNote=true; ds.brokenPortaArp=false; ds.snNoLowPeriods=true; ds.disableSampleMacro=true; ds.preNoteNoEffect=true; ds.oldDPCM=true; ds.delayBehavior=0; ds.jumpTreatment=2; ds.oldAlwaysSetVolume=true; // 1.1 compat flags if (ds.version>24) { ds.waveDutyIsVol=true; ds.legacyVolumeSlides=false; } // Neo Geo detune is caused by Defle running Neo Geo at the wrong clock. /* if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { ds.tuning=443.23; } */ // Genesis detuned on Defle v10 and earlier /*if (ds.version<19 && ds.system[0]==DIV_SYSTEM_GENESIS) { ds.tuning=443.23; }*/ // C64 detuned on Defle v11 and earlier /*if (ds.version<21 && (ds.system[0]==DIV_SYSTEM_C64_6581 || ds.system[0]==DIV_SYSTEM_C64_8580)) { ds.tuning=433.2; }*/ // Game Boy arp+soundLen screwery if (ds.system[0]==DIV_SYSTEM_GB) { ds.systemFlags[0].set("enoughAlready",true); } } logI("reading module data..."); if (ds.version>0x0c) { ds.subsong[0]->hilightA=reader.readC(); ds.subsong[0]->hilightB=reader.readC(); } bool customTempo=false; ds.subsong[0]->timeBase=reader.readC(); ds.subsong[0]->speeds.len=2; ds.subsong[0]->speeds.val[0]=reader.readC(); if (ds.version>0x07) { ds.subsong[0]->speeds.val[1]=reader.readC(); bool pal=reader.readC(); ds.subsong[0]->hz=pal?60:50; customTempo=reader.readC(); } else { ds.subsong[0]->speeds.len=1; } if (ds.version>0x0a) { String hz=reader.readString(3); if (customTempo) { try { ds.subsong[0]->hz=std::stoi(hz); } catch (std::exception& e) { logW("invalid custom Hz!"); ds.subsong[0]->hz=60; } } } if (ds.version>0x17) { ds.subsong[0]->patLen=reader.readI(); } else { ds.subsong[0]->patLen=(unsigned char)reader.readC(); } ds.subsong[0]->ordersLen=(unsigned char)reader.readC(); if (ds.subsong[0]->patLen<0) { logE("pattern length is negative!"); lastError="pattern lengrh is negative!"; delete[] file; return false; } if (ds.subsong[0]->patLen>256) { logE("pattern length is too large!"); lastError="pattern length is too large!"; delete[] file; return false; } if (ds.subsong[0]->ordersLen<0) { logE("song length is negative!"); lastError="song length is negative!"; delete[] file; return false; } if (ds.subsong[0]->ordersLen>127) { logE("song is too long!"); lastError="song is too long!"; delete[] file; return false; } if (ds.version<20 && ds.version>3) { ds.subsong[0]->arpLen=reader.readC(); } else { ds.subsong[0]->arpLen=1; } if (ds.system[0]==DIV_SYSTEM_YMU759) { switch (ds.subsong[0]->timeBase) { case 0: ds.subsong[0]->hz=248; break; case 1: ds.subsong[0]->hz=200; break; case 2: ds.subsong[0]->hz=100; break; case 3: ds.subsong[0]->hz=50; break; case 4: ds.subsong[0]->hz=25; break; case 5: ds.subsong[0]->hz=20; break; default: ds.subsong[0]->hz=248; break; } ds.subsong[0]->timeBase=0; addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system."); } logV("%x",reader.tell()); logI("reading pattern matrix (%d * %d = %d)...",ds.subsong[0]->ordersLen,getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen*getChannelCount(ds.system[0])); for (int i=0; iordersLen; j++) { ds.subsong[0]->orders.ord[i][j]=reader.readC(); if (ds.subsong[0]->orders.ord[i][j]>0x7f) { logE("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]); lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]); delete[] file; return false; } if (ds.version>0x18) { // 1.1 pattern names ds.subsong[0]->pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC()); } } if (ds.version>0x03 && ds.version<0x06 && i<16) { historicColIns[i]=reader.readC(); } } logV("%x",reader.tell()); if (ds.version>0x05) { ds.insLen=(unsigned char)reader.readC(); } else { ds.insLen=16; } logI("reading instruments (%d)...",ds.insLen); if (ds.insLen>0) ds.ins.reserve(ds.insLen); for (int i=0; i0x05) { ins->name=reader.readString((unsigned char)reader.readC()); } logD("%d name: %s",i,ins->name.c_str()); if (ds.version<0x0b) { // instruments in ancient versions were all FM. mode=1; } else { mode=reader.readC(); if (mode>1) logW("%d: invalid instrument mode %d!",i,mode); } ins->type=mode?DIV_INS_FM:DIV_INS_STD; if (ds.system[0]==DIV_SYSTEM_GB) { ins->type=DIV_INS_GB; } if (ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) { ins->type=DIV_INS_C64; } if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { if (!mode) { ins->type=DIV_INS_AY; } } if (ds.system[0]==DIV_SYSTEM_PCE) { ins->type=DIV_INS_PCE; } if ((ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) && ins->type==DIV_INS_FM) { ins->type=DIV_INS_OPLL; } if (ds.system[0]==DIV_SYSTEM_YMU759) { ins->type=DIV_INS_OPL; } if (ds.system[0]==DIV_SYSTEM_ARCADE) { ins->type=DIV_INS_OPM; } if ((ds.system[0]==DIV_SYSTEM_NES || ds.system[0]==DIV_SYSTEM_NES_VRC7 || ds.system[0]==DIV_SYSTEM_NES_FDS) && ins->type==DIV_INS_STD) { ins->type=DIV_INS_NES; } if (mode) { // FM if (ds.version>0x05) { ins->fm.alg=reader.readC(); if (ds.version<0x13) { reader.readC(); } ins->fm.fb=reader.readC(); if (ds.version<0x13) { reader.readC(); } ins->fm.fms=reader.readC(); if (ds.version<0x13) { reader.readC(); ins->fm.ops=2+reader.readC()*2; if (ds.system[0]!=DIV_SYSTEM_YMU759) ins->fm.ops=4; } else { ins->fm.ops=4; } ins->fm.ams=reader.readC(); } else { ins->fm.alg=reader.readC(); reader.readC(); ins->fm.fb=reader.readC(); reader.readC(); // apparently an index of sorts starting from 0x59? ins->fm.fms=reader.readC(); reader.readC(); // 0x59+index? ins->fm.ops=2+reader.readC()*2; } logD("ALG %d FB %d FMS %d AMS %d OPS %d",ins->fm.alg,ins->fm.fb,ins->fm.fms,ins->fm.ams,ins->fm.ops); if (ins->fm.ops!=2 && ins->fm.ops!=4) { logE("invalid op count %d. did we read it wrong?",ins->fm.ops); lastError="file is corrupt or unreadable at operators"; delete[] file; return false; } for (int j=0; jfm.ops; j++) { ins->fm.op[j].am=reader.readC(); ins->fm.op[j].ar=reader.readC(); if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { ins->fm.op[j].ar&=15; } if (ds.version<0x13) { ins->fm.op[j].dam=reader.readC(); } ins->fm.op[j].dr=reader.readC(); if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { ins->fm.op[j].dr&=15; } if (ds.version<0x13) { ins->fm.op[j].dvb=reader.readC(); ins->fm.op[j].egt=reader.readC(); ins->fm.op[j].ksl=reader.readC(); if (ds.version<0x11) { // don't know when did this change ins->fm.op[j].ksr=reader.readC(); } } ins->fm.op[j].mult=reader.readC(); ins->fm.op[j].rr=reader.readC(); ins->fm.op[j].sl=reader.readC(); if (ds.version<0x13) { ins->fm.op[j].sus=reader.readC(); } ins->fm.op[j].tl=reader.readC(); if (ds.version<0x13) { ins->fm.op[j].vib=reader.readC(); ins->fm.op[j].ws=reader.readC(); } else { if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { if (j==0) { ins->fm.opllPreset=reader.readC(); } else { reader.readC(); } } else { ins->fm.op[j].dt2=reader.readC(); } } if (ds.version>0x05) { if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { ins->fm.op[j].ksr=reader.readC(); ins->fm.op[j].vib=reader.readC(); ins->fm.op[j].ksl=reader.readC(); ins->fm.op[j].ssgEnv=reader.readC(); } else { ins->fm.op[j].rs=reader.readC(); ins->fm.op[j].dt=reader.readC(); ins->fm.op[j].d2r=reader.readC(); ins->fm.op[j].ssgEnv=reader.readC(); } } if (ds.version<0x12) { // before version 10 all ops were responsive to volume ins->fm.op[j].kvs=1; } logD("OP%d: AM %d AR %d DAM %d DR %d DVB %d EGT %d KSL %d MULT %d RR %d SL %d SUS %d TL %d VIB %d WS %d RS %d DT %d D2R %d SSG-EG %d",j, ins->fm.op[j].am, ins->fm.op[j].ar, ins->fm.op[j].dam, ins->fm.op[j].dr, ins->fm.op[j].dvb, ins->fm.op[j].egt, ins->fm.op[j].ksl, ins->fm.op[j].mult, ins->fm.op[j].rr, ins->fm.op[j].sl, ins->fm.op[j].sus, ins->fm.op[j].tl, ins->fm.op[j].vib, ins->fm.op[j].ws, ins->fm.op[j].rs, ins->fm.op[j].dt, ins->fm.op[j].d2r, ins->fm.op[j].ssgEnv ); } // swap alg operator 2 and 3 if YMU759 if (ds.system[0]==DIV_SYSTEM_YMU759 && ins->fm.ops==4) { DivInstrumentFM::Operator oldOp=ins->fm.op[2]; ins->fm.op[2]=ins->fm.op[1]; ins->fm.op[1]=oldOp; if (ins->fm.alg==1) { ins->fm.alg=2; } else if (ins->fm.alg==2) { ins->fm.alg=1; } } } else { // STD if (ds.system[0]!=DIV_SYSTEM_GB || ds.version<0x12) { ins->std.volMacro.len=reader.readC(); for (int j=0; jstd.volMacro.len; j++) { if (ds.version<0x0e) { ins->std.volMacro.val[j]=reader.readC(); } else { ins->std.volMacro.val[j]=reader.readI(); } } if (ins->std.volMacro.len>0) { ins->std.volMacro.open=true; ins->std.volMacro.loop=reader.readC(); } else { ins->std.volMacro.open=false; } } ins->std.arpMacro.len=reader.readC(); for (int j=0; jstd.arpMacro.len; j++) { if (ds.version<0x0e) { ins->std.arpMacro.val[j]=reader.readC(); } else { ins->std.arpMacro.val[j]=reader.readI(); } } if (ins->std.arpMacro.len>0) { ins->std.arpMacro.loop=reader.readC(); ins->std.arpMacro.open=true; } else { ins->std.arpMacro.open=false; } if (ds.version>0x0f) { ins->std.arpMacro.mode=reader.readC(); } if (!ins->std.arpMacro.mode) { for (int j=0; jstd.arpMacro.len; j++) { ins->std.arpMacro.val[j]-=12; } } else { ins->std.arpMacro.mode=0; for (int j=0; jstd.arpMacro.len; j++) { ins->std.arpMacro.val[j]^=0x40000000; } if (ins->std.arpMacro.loop==255 && ins->std.arpMacro.len<255) { ins->std.arpMacro.val[ins->std.arpMacro.len++]=0; } } ins->std.dutyMacro.len=reader.readC(); for (int j=0; jstd.dutyMacro.len; j++) { if (ds.version<0x0e) { ins->std.dutyMacro.val[j]=reader.readC(); } else { ins->std.dutyMacro.val[j]=reader.readI(); } /*if ((ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) && ins->std.dutyMacro.val[j]>24) { ins->std.dutyMacro.val[j]=24; }*/ } if (ins->std.dutyMacro.len>0) { ins->std.dutyMacro.open=true; ins->std.dutyMacro.loop=reader.readC(); } else { ins->std.dutyMacro.open=false; } ins->std.waveMacro.len=reader.readC(); for (int j=0; jstd.waveMacro.len; j++) { if (ds.version<0x0e) { ins->std.waveMacro.val[j]=reader.readC(); } else { ins->std.waveMacro.val[j]=reader.readI(); } // piece of crap offset by 1 if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT) { ins->std.waveMacro.val[j]++; } } if (ins->std.waveMacro.len>0) { ins->std.waveMacro.open=true; ins->std.waveMacro.loop=reader.readC(); } else { ins->std.waveMacro.open=false; } if (ds.system[0]==DIV_SYSTEM_C64_6581 || ds.system[0]==DIV_SYSTEM_C64_8580) { bool volIsCutoff=false; ins->c64.triOn=reader.readC(); ins->c64.sawOn=reader.readC(); ins->c64.pulseOn=reader.readC(); ins->c64.noiseOn=reader.readC(); ins->c64.a=reader.readC(); ins->c64.d=reader.readC(); ins->c64.s=reader.readC(); ins->c64.r=reader.readC(); ins->c64.duty=(reader.readC()*4095)/100; ins->c64.ringMod=reader.readC(); ins->c64.oscSync=reader.readC(); ins->c64.toFilter=reader.readC(); if (ds.version<0x11) { volIsCutoff=reader.readI(); } else { volIsCutoff=reader.readC(); } ins->c64.initFilter=reader.readC(); ins->c64.res=reader.readC(); ins->c64.cut=(reader.readC()*2047)/100; ins->c64.hp=reader.readC(); ins->c64.bp=reader.readC(); ins->c64.lp=reader.readC(); ins->c64.ch3off=reader.readC(); // weird storage if (volIsCutoff) { // move to alg (new cutoff) ins->std.algMacro.len=ins->std.volMacro.len; ins->std.algMacro.loop=ins->std.volMacro.loop; ins->std.algMacro.rel=ins->std.volMacro.rel; for (int j=0; jstd.algMacro.len; j++) { ins->std.algMacro.val[j]=-(ins->std.volMacro.val[j]-18); } ins->std.volMacro.len=0; memset(ins->std.volMacro.val,0,256*sizeof(int)); } for (int j=0; jstd.dutyMacro.len; j++) { ins->std.dutyMacro.val[j]-=12; } } if (ds.system[0]==DIV_SYSTEM_GB && ds.version>0x11) { ins->gb.envVol=reader.readC(); ins->gb.envDir=reader.readC(); ins->gb.envLen=reader.readC(); ins->gb.soundLen=reader.readC(); ins->std.volMacro.open=false; logD("GB data: vol %d dir %d len %d sl %d",ins->gb.envVol,ins->gb.envDir,ins->gb.envLen,ins->gb.soundLen); } else if (ds.system[0]==DIV_SYSTEM_GB) { // set software envelope flag ins->gb.softEnv=true; // try to convert macro to envelope in case the user decides to switch to them if (ins->std.volMacro.len>0) { ins->gb.envVol=ins->std.volMacro.val[0]; if (ins->std.volMacro.val[0]std.volMacro.val[1]) { ins->gb.envDir=true; } if (ins->std.volMacro.val[ins->std.volMacro.len-1]==0) { ins->gb.soundLen=ins->std.volMacro.len*2; } } } } ds.ins.push_back(ins); } if (ds.version>0x0b) { ds.waveLen=(unsigned char)reader.readC(); logI("reading wavetables (%d)...",ds.waveLen); if (ds.waveLen>0) ds.wave.reserve(ds.waveLen); for (int i=0; ilen=(unsigned char)reader.readI(); if (ds.system[0]==DIV_SYSTEM_GB) { wave->max=15; } if (ds.system[0]==DIV_SYSTEM_NES_FDS) { wave->max=63; } if (wave->len>65) { logE("invalid wave length %d. are we doing something wrong?",wave->len); lastError="file is corrupt or unreadable at wavetables"; delete[] file; return false; } logD("%d length %d",i,wave->len); for (int j=0; jlen; j++) { if (ds.version<0x0e) { wave->data[j]=reader.readC(); } else { wave->data[j]=reader.readI(); } wave->data[j]&=wave->max; } // #FDS4Bit if (ds.system[0]==DIV_SYSTEM_NES_FDS && ds.version<0x1a) { for (int j=0; jlen; j++) { wave->data[j]*=4; } } ds.wave.push_back(wave); } // sometimes there's a single length 0 wavetable in the file. I don't know why. if (ds.waveLen==1) { if (ds.wave[0]->len==0) { ds.clearWavetables(); } } } logV("%x",reader.tell()); logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen); for (int i=0; ipat[i]; if (ds.version<0x0a) { chan.effectCols=1; } else { chan.effectCols=reader.readC(); } logD("%d fx rows: %d",i,chan.effectCols); if (chan.effectCols>4 || chan.effectCols<1) { logE("invalid effect column count %d. are you sure everything is ok?",chan.effectCols); lastError="file is corrupt or unreadable at effect columns"; delete[] file; return false; } for (int j=0; jordersLen; j++) { DivPattern* pat=chan.getPattern(ds.subsong[0]->orders.ord[i][j],true); if (ds.version>0x08) { // current pattern format for (int k=0; kpatLen; k++) { // note pat->data[k][0]=reader.readS(); // octave pat->data[k][1]=reader.readS(); if (ds.system[0]==DIV_SYSTEM_SMS && ds.version<0x0e && pat->data[k][1]>0) { // apparently it was up one octave before pat->data[k][1]--; } else if (ds.system[0]==DIV_SYSTEM_GENESIS && ds.version<0x0e && pat->data[k][1]>0 && i>5) { // ditto pat->data[k][1]--; } if (ds.version<0x12) { if (ds.system[0]==DIV_SYSTEM_GB && i==3 && pat->data[k][1]>0) { // back then noise was 2 octaves lower pat->data[k][1]-=2; } } if (ds.system[0]==DIV_SYSTEM_YMU759 && pat->data[k][0]!=0) { // apparently YMU759 is stored 2 octaves lower pat->data[k][1]+=2; } if (pat->data[k][0]==0 && pat->data[k][1]!=0) { logD("what? %d:%d:%d note %d octave %d",i,j,k,pat->data[k][0],pat->data[k][1]); pat->data[k][0]=12; pat->data[k][1]--; } // volume pat->data[k][3]=reader.readS(); if (ds.version<0x0a) { // back then volume was stored as 00-ff instead of 00-7f/0-f if (i>5) { pat->data[k][3]>>=4; } else { pat->data[k][3]>>=1; } } if (ds.version<0x12) { if (ds.system[0]==DIV_SYSTEM_GB && i==2 && pat->data[k][3]>0) { // volume range of GB wave channel was 0-3 rather than 0-F pat->data[k][3]=(pat->data[k][3]&3)*5; } } for (int l=0; ldata[k][4+(l<<1)]=reader.readS(); pat->data[k][5+(l<<1)]=reader.readS(); if (ds.version<0x14) { if (pat->data[k][4+(l<<1)]==0xe5 && pat->data[k][5+(l<<1)]!=-1) { pat->data[k][5+(l<<1)]=128+((pat->data[k][5+(l<<1)]-128)/4); } } } // instrument pat->data[k][2]=reader.readS(); // this is sad if (ds.system[0]==DIV_SYSTEM_NES_FDS) { if (i==5 && pat->data[k][2]!=-1) { if (pat->data[k][2]>=0 && pat->data[k][2]data[k][2]]->type=DIV_INS_FDS; } } } } } else { // historic pattern format if (i<16) pat->data[0][2]=historicColIns[i]; for (int k=0; kpatLen; k++) { // note pat->data[k][0]=reader.readC(); // octave pat->data[k][1]=reader.readC(); if (pat->data[k][0]!=0) { // YMU759 is stored 2 octaves lower pat->data[k][1]+=2; } if (pat->data[k][0]==0 && pat->data[k][1]!=0) { logD("what? %d:%d:%d note %d octave %d",i,j,k,pat->data[k][0],pat->data[k][1]); pat->data[k][0]=12; pat->data[k][1]--; } // volume and effect unsigned char vol=reader.readC(); unsigned char fx=reader.readC(); unsigned char fxVal=reader.readC(); pat->data[k][3]=(vol==0x80 || vol==0xff)?-1:vol; // effect pat->data[k][4]=(fx==0x80 || fx==0xff)?-1:fx; pat->data[k][5]=(fxVal==0x80 || fx==0xff)?-1:fxVal; // instrument if (ds.version>0x05) { pat->data[k][2]=reader.readC(); if (pat->data[k][2]==0x80 || pat->data[k][2]==0xff) pat->data[k][2]=-1; } } } } } int ymuSampleRate=20; ds.sampleLen=(unsigned char)reader.readC(); logI("reading samples (%d)...",ds.sampleLen); if (ds.version<0x0b && ds.sampleLen>0) { // it appears this byte stored the YMU759 sample rate ymuSampleRate=reader.readC(); } if (ds.sampleLen>0) ds.sample.reserve(ds.sampleLen); for (int i=0; i0x16) { sample->name=reader.readString((unsigned char)reader.readC()); } else { sample->name=""; } logD("%d name %s (%d)",i,sample->name.c_str(),length); sample->rate=22050; if (ds.version>=0x0b) { sample->rate=fileToDivRate(reader.readC()); sample->centerRate=sample->rate; pitch=reader.readC(); vol=reader.readC(); } if (ds.version<=0x08) { sample->rate=ymuSampleRate*400; } if (ds.version>0x15) { sample->depth=(DivSampleDepth)reader.readC(); if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) { logW("%d: sample depth is wrong! (%d)",i,(int)sample->depth); sample->depth=DIV_SAMPLE_DEPTH_16BIT; } } else { if (ds.version>0x08) { sample->depth=DIV_SAMPLE_DEPTH_16BIT; } else { // it appears samples were stored as ADPCM back then sample->depth=DIV_SAMPLE_DEPTH_YMZ_ADPCM; } } if (ds.version>=0x1b) { // what the hell man... cutStart=reader.readI(); cutEnd=reader.readI(); logV("cutStart: %d cutEnd: %d",cutStart,cutEnd); } if (length>0) { if (ds.version>0x08) { if (ds.version<0x0b) { data=new short[1+(length/2)]; reader.read(data,length); length/=2; } else { data=new short[length]; reader.read(data,length*2); } #ifdef TA_BIG_ENDIAN // convert to big-endian for (int pos=0; pos>8)); } #endif int scaledLen=ceil((double)length/samplePitches[pitch]); if (scaledLen>0) { // resample logD("%d: scaling from %d...",i,pitch); short* newData=new short[scaledLen]; memset(newData,0,scaledLen*sizeof(short)); int k=0; float mult=(float)(vol)/50.0f; for (double j=0; j=scaledLen) { break; } if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { float next=(float)(data[(unsigned int)j]-0x80)*mult; newData[k++]=fmin(fmax(next,-128),127); } else { float next=(float)data[(unsigned int)j]*mult; newData[k++]=fmin(fmax(next,-32768),32767); } } delete[] data; data=newData; } logV("length: %d. scaledLen: %d.",length,scaledLen); if (ds.version>=0x1b) { if (cutStart<0 || cutStart>scaledLen) { logE("cutStart is out of range! (%d, scaledLen: %d)",cutStart,scaledLen); lastError="file is corrupt or unreadable at samples"; delete[] file; return false; } if (cutEnd<0 || cutEnd>scaledLen) { logE("cutEnd is out of range! (%d, scaledLen: %d)",cutEnd,scaledLen); lastError="file is corrupt or unreadable at samples"; delete[] file; return false; } if (cutEndinit(scaledLen)) { logE("%d: error while initializing sample!",i); } else { for (int i=0; idepth==DIV_SAMPLE_DEPTH_8BIT) { sample->data8[i]=data[i]; } else { sample->data16[i]=data[i]; } } } delete[] data; } else { // YMZ ADPCM adpcmData=new unsigned char[length]; logV("%x",reader.tell()); reader.read(adpcmData,length); for (int i=0; i>4); } if (!sample->init(length*2)) { logE("%d: error while initializing sample!",i); } memcpy(sample->dataZ,adpcmData,length); delete[] adpcmData; } } ds.sample.push_back(sample); } if (reader.tell()26) version=26; if (version<24) { logE("cannot save in this version!"); lastError="invalid version to save in! this is a bug!"; return NULL; } // check whether system is compound bool isFlat=false; if (song.systemLen==2) { if (song.system[0]==DIV_SYSTEM_YM2612 && song.system[1]==DIV_SYSTEM_SMS) { isFlat=true; } if (song.system[0]==DIV_SYSTEM_YM2612_EXT && song.system[1]==DIV_SYSTEM_SMS) { isFlat=true; } if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { isFlat=true; } if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { isFlat=true; } if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { isFlat=true; } if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { isFlat=true; } } // fail if more than one system if (!isFlat && song.systemLen!=1) { logE("cannot save multiple systems in this format!"); lastError="multiple systems not possible on .dmf"; return NULL; } // fail if this is an YMU759 song if (song.system[0]==DIV_SYSTEM_YMU759) { logE("cannot save YMU759 song!"); lastError="YMU759 song saving is not supported"; return NULL; } // fail if the system is SMS+OPLL and version<25 if (version<25 && song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { logE("Master System FM expansion not supported in 1.0/legacy .dmf!"); lastError="Master System FM expansion not supported in 1.0/legacy .dmf!"; return NULL; } // fail if the system is NES+VRC7 and version<25 if (version<25 && song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { logE("NES + VRC7 not supported in 1.0/legacy .dmf!"); lastError="NES + VRC7 not supported in 1.0/legacy .dmf!"; return NULL; } // fail if the system is FDS and version<25 if (version<25 && song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { logE("FDS not supported in 1.0/legacy .dmf!"); lastError="FDS not supported in 1.0/legacy .dmf!"; return NULL; } // fail if the system is Furnace-exclusive if (!isFlat && systemToFileDMF(song.system[0])==0) { logE("cannot save Furnace-exclusive system song!"); lastError="this system is not possible on .dmf"; return NULL; } // fail if values are out of range if (curSubSong->ordersLen>127) { logE("maximum .dmf song length is 127!"); lastError="maximum .dmf song length is 127"; return NULL; } if (song.ins.size()>128) { logE("maximum number of instruments in .dmf is 128!"); lastError="maximum number of instruments in .dmf is 128"; return NULL; } if (song.wave.size()>64) { logE("maximum number of wavetables in .dmf is 64!"); lastError="maximum number of wavetables in .dmf is 64"; return NULL; } for (int i=0; iordersLen; j++) { if (curOrders->ord[i][j]>0x7f) { logE("order %d, %d is out of range (0-127)!",curOrders->ord[i][j]); lastError=fmt::sprintf("order %d, %d is out of range (0-127)",curOrders->ord[i][j]); return NULL; } } } saveLock.lock(); warnings=""; song.version=version; song.isDMF=true; SafeWriter* w=new SafeWriter; w->init(); // write magic w->write(DIV_DMF_MAGIC,16); // version w->writeC(version); DivSystem sys=DIV_SYSTEM_NULL; if (song.system[0]==DIV_SYSTEM_YM2612 && song.system[1]==DIV_SYSTEM_SMS) { w->writeC(systemToFileDMF(DIV_SYSTEM_GENESIS)); sys=DIV_SYSTEM_GENESIS; } else if (song.system[0]==DIV_SYSTEM_YM2612_EXT && song.system[1]==DIV_SYSTEM_SMS) { w->writeC(systemToFileDMF(DIV_SYSTEM_GENESIS_EXT)); sys=DIV_SYSTEM_GENESIS_EXT; } else if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { w->writeC(systemToFileDMF(DIV_SYSTEM_ARCADE)); sys=DIV_SYSTEM_ARCADE; } else if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { w->writeC(systemToFileDMF(DIV_SYSTEM_SMS_OPLL)); sys=DIV_SYSTEM_SMS_OPLL; } else if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { w->writeC(systemToFileDMF(DIV_SYSTEM_NES_VRC7)); sys=DIV_SYSTEM_NES_VRC7; } else if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { w->writeC(systemToFileDMF(DIV_SYSTEM_NES_FDS)); sys=DIV_SYSTEM_NES_FDS; } else { w->writeC(systemToFileDMF(song.system[0])); sys=song.system[0]; } // song info w->writeString(song.name,true); w->writeString(song.author,true); w->writeC(curSubSong->hilightA); w->writeC(curSubSong->hilightB); int intHz=curSubSong->hz; w->writeC(curSubSong->timeBase); w->writeC(curSubSong->speeds.val[0]); w->writeC((curSubSong->speeds.len>=2)?curSubSong->speeds.val[1]:curSubSong->speeds.val[0]); w->writeC((intHz<=53)?0:1); w->writeC((intHz!=60 && intHz!=50)); char customHz[4]; memset(customHz,0,4); snprintf(customHz,4,"%d",(int)curSubSong->hz); w->write(customHz,3); w->writeI(curSubSong->patLen); w->writeC(curSubSong->ordersLen); for (int i=0; iordersLen; j++) { w->writeC(curOrders->ord[i][j]); if (version>=25) { DivPattern* pat=curPat[i].getPattern(j,false); w->writeString(pat->name,true); } } } if (song.subsong.size()>1) { addWarning("only the currently selected subsong will be saved"); } if (!song.grooves.empty()) { addWarning("grooves will not be saved"); } if (curSubSong->speeds.len>2) { addWarning("only the first two speeds will be effective"); } if (curSubSong->virtualTempoD!=curSubSong->virtualTempoN) { addWarning(".dmf format does not support virtual tempo"); } if (song.tuning<439.99 && song.tuning>440.01) { addWarning(".dmf format does not support tuning"); } if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { addWarning("absolute duty/cutoff macro not available in .dmf!"); addWarning("duty precision will be lost"); } for (DivInstrument* i: song.ins) { if (i->type==DIV_INS_AMIGA) { addWarning(".dmf format does not support arbitrary-pitch sample mode"); break; } } for (DivInstrument* i: song.ins) { if (i->type==DIV_INS_FM || i->type==DIV_INS_OPM) { addWarning("no FM macros in .dmf format"); break; } } w->writeC(song.ins.size()); for (DivInstrument* i: song.ins) { w->writeString(i->name,true); // safety check if (!isFMSystem(sys) && i->type!=DIV_INS_STD && i->type!=DIV_INS_NES && i->type!=DIV_INS_FDS) { switch (song.system[0]) { case DIV_SYSTEM_GB: i->type=DIV_INS_GB; break; case DIV_SYSTEM_NES: i->type=DIV_INS_NES; break; case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: i->type=DIV_INS_C64; break; case DIV_SYSTEM_PCE: i->type=DIV_INS_PCE; break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: i->type=DIV_INS_AY; break; default: i->type=DIV_INS_STD; break; } } if (!isSTDSystem(sys) && i->type!=DIV_INS_FM && i->type!=DIV_INS_OPM) { if (sys==DIV_SYSTEM_ARCADE) { i->type=DIV_INS_OPM; } else { i->type=DIV_INS_FM; } } w->writeC((i->type==DIV_INS_FM || i->type==DIV_INS_OPM || i->type==DIV_INS_OPLL)?1:0); if (i->type==DIV_INS_FM || i->type==DIV_INS_OPM || i->type==DIV_INS_OPLL) { // FM w->writeC(i->fm.alg); w->writeC(i->fm.fb); w->writeC(i->fm.fms); w->writeC(i->fm.ams); for (int j=0; j<4; j++) { DivInstrumentFM::Operator& op=i->fm.op[j]; w->writeC(op.am); w->writeC(op.ar); w->writeC(op.dr); w->writeC(op.mult); w->writeC(op.rr); w->writeC(op.sl); w->writeC(op.tl); if ((sys==DIV_SYSTEM_SMS_OPLL || sys==DIV_SYSTEM_NES_VRC7) && j==0) { w->writeC(i->fm.opllPreset); } else { w->writeC(op.dt2); } if (sys==DIV_SYSTEM_SMS_OPLL || sys==DIV_SYSTEM_NES_VRC7) { w->writeC(op.ksr); w->writeC(op.vib); w->writeC(op.ksl); w->writeC(op.ssgEnv); } else { w->writeC(op.rs); w->writeC(op.dt); w->writeC(op.d2r); w->writeC(op.ssgEnv); } } } else { // STD bool volIsCutoff=false; if (sys!=DIV_SYSTEM_GB) { int realVolMacroLen=i->std.volMacro.len; if (realVolMacroLen>127) realVolMacroLen=127; if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { if (i->std.algMacro.len>0) volIsCutoff=true; if (volIsCutoff) { if (i->std.volMacro.len>0) { addWarning(".dmf only supports volume or cutoff macro in C64, but not both. volume macro will be lost."); } realVolMacroLen=i->std.algMacro.len; if (realVolMacroLen>127) realVolMacroLen=127; w->writeC(realVolMacroLen); for (int j=0; jwriteI((-i->std.algMacro.val[j])+18); } } else { w->writeC(realVolMacroLen); for (int j=0; jwriteI(i->std.volMacro.val[j]); } } } else { w->writeC(realVolMacroLen); for (int j=0; jwriteI(i->std.volMacro.val[j]); } } if (realVolMacroLen>0) { if (volIsCutoff) { w->writeC(i->std.algMacro.loop); } else { w->writeC(i->std.volMacro.loop); } } } bool arpMacroMode=false; int arpMacroHowManyFixed=0; int realArpMacroLen=i->std.arpMacro.len; for (int j=0; jstd.arpMacro.len; j++) { if ((i->std.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) { arpMacroHowManyFixed++; } } if (arpMacroHowManyFixed>=i->std.arpMacro.len-1) { arpMacroMode=true; } if (i->std.arpMacro.len>0) { if (arpMacroMode && i->std.arpMacro.val[i->std.arpMacro.len-1]==0 && i->std.arpMacro.loop>=i->std.arpMacro.len) { realArpMacroLen--; } } if (realArpMacroLen>127) realArpMacroLen=127; w->writeC(realArpMacroLen); if (arpMacroMode) { for (int j=0; jstd.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) { w->writeI(i->std.arpMacro.val[j]^0x40000000); } else { w->writeI(i->std.arpMacro.val[j]); } } } else { for (int j=0; jstd.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) { w->writeI((i->std.arpMacro.val[j]^0x40000000)+12); } else { w->writeI(i->std.arpMacro.val[j]+12); } } } if (realArpMacroLen>0) { w->writeC(i->std.arpMacro.loop); } w->writeC(arpMacroMode); int realDutyMacroLen=i->std.dutyMacro.len; if (realDutyMacroLen>127) realDutyMacroLen=127; w->writeC(realDutyMacroLen); if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { for (int j=0; jwriteI(i->std.dutyMacro.val[j]+12); } } else { for (int j=0; jwriteI(i->std.dutyMacro.val[j]); } } if (realDutyMacroLen>0) { w->writeC(i->std.dutyMacro.loop); } int realWaveMacroLen=i->std.waveMacro.len; if (realWaveMacroLen>127) realWaveMacroLen=127; w->writeC(realWaveMacroLen); for (int j=0; jwriteI(i->std.waveMacro.val[j]-1); } else { w->writeI(i->std.waveMacro.val[j]); } } if (realWaveMacroLen>0) { w->writeC(i->std.waveMacro.loop); } if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { w->writeC(i->c64.triOn); w->writeC(i->c64.sawOn); w->writeC(i->c64.pulseOn); w->writeC(i->c64.noiseOn); w->writeC(i->c64.a); w->writeC(i->c64.d); w->writeC(i->c64.s); w->writeC(i->c64.r); logW("duty and cutoff precision will be lost!"); w->writeC((i->c64.duty*100)/4095); w->writeC(i->c64.ringMod); w->writeC(i->c64.oscSync); w->writeC(i->c64.toFilter); w->writeC(volIsCutoff); w->writeC(i->c64.initFilter); w->writeC(i->c64.res); w->writeC((i->c64.cut*100)/2047); w->writeC(i->c64.hp); w->writeC(i->c64.bp); w->writeC(i->c64.lp); w->writeC(i->c64.ch3off); } if (sys==DIV_SYSTEM_GB) { w->writeC(i->gb.envVol); w->writeC(i->gb.envDir); w->writeC(i->gb.envLen); w->writeC(i->gb.soundLen); } } } w->writeC(song.wave.size()); for (DivWavetable* i: song.wave) { w->writeI(i->len); if (sys==DIV_SYSTEM_NES_FDS && version<26) { for (int j=0; jlen; j++) { w->writeI(i->data[j]>>2); } } else { for (int j=0; jlen; j++) { w->writeI(i->data[j]); } } } bool relWarning=false; for (int i=0; iwriteC(curPat[i].effectCols); for (int j=0; jordersLen; j++) { DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false); for (int k=0; kpatLen; k++) { if ((pat->data[k][0]==101 || pat->data[k][0]==102) && pat->data[k][1]==0) { w->writeS(100); w->writeS(0); if (!relWarning) { relWarning=true; addWarning("note/macro release will be converted to note off!"); } } else { w->writeS(pat->data[k][0]); // note w->writeS(pat->data[k][1]); // octave } w->writeS(pat->data[k][3]); // volume #ifdef TA_BIG_ENDIAN for (int l=0; lwriteS(pat->data[k][4+l]); } #else w->write(&pat->data[k][4],2*curPat[i].effectCols*2); // effects #endif w->writeS(pat->data[k][2]); // instrument } } } if (song.sample.size()>0) { addWarning("samples' rates will be rounded to nearest compatible value"); } w->writeC(song.sample.size()); for (DivSample* i: song.sample) { w->writeI(i->samples); w->writeString(i->name,true); w->writeC(divToFileRate(i->rate)); w->writeC(5); w->writeC(50); // i'm too lazy to deal with .dmf's weird way of storing 8-bit samples w->writeC(16); // well I can't be lazy if it's on a big-endian system #ifdef TA_BIG_ENDIAN for (unsigned int j=0; jlength16; j++) { w->writeC(((unsigned short)i->data16[j])&0xff); w->writeC(((unsigned short)i->data16[j])>>8); } #else w->write(i->data16,i->length16); #endif } saveLock.unlock(); return w; }