/** * Furnace Tracker - multi-system chiptune tracker * Copyright (C) 2021-2023 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 "dataErrors.h" #include "engine.h" #include "../ta-log.h" #include "instrument.h" #include "song.h" #include #include #define DIV_READ_SIZE 131072 #define DIV_DMF_MAGIC ".DelekDefleMask." #define DIV_FUR_MAGIC "-Furnace module-" #define DIV_FTM_MAGIC "FamiTracker Module" #define DIV_FC13_MAGIC "SMOD" #define DIV_FC14_MAGIC "FC14" #define DIV_S3M_MAGIC "SCRM" struct InflateBlock { unsigned char* buf; size_t len; size_t blockSize; InflateBlock(size_t s) { buf=new unsigned char[s]; len=s; blockSize=0; } ~InflateBlock() { delete[] buf; len=0; } }; struct NotZlibException { int what; NotZlibException(int w): what(w) {} }; 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 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.delayBehavior=0; ds.jumpTreatment=2; // 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); 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(); } } 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) { 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) { ins->c64.volIsCutoff=reader.readI(); } else { ins->c64.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 (ins->c64.volIsCutoff) { for (int j=0; jstd.volMacro.len; j++) { ins->std.volMacro.val[j]-=18; } } 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); 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(); } 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,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()>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; } } 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 }; 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; } 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:"); for (int i=0; iDIV_MAX_CHANS) { tchans=DIV_MAX_CHANS; logW("too many channels!"); } // 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(); } 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; i=136) song.patchbayAuto=reader.readC(); if (ds.version>=138) { ds.brokenPortaLegato=reader.readC(); if (ds.version>=155) { ds.brokenFMOff=reader.readC(); } else { reader.readC(); } for (int i=0; i<6; 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(); 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) { 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(); } 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 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 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 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 (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 patPtr; char magic[4]={0,0,0,0}; short defaultVols[31]; int sampLens[31]; // 0=arp, 1=pslide, 2=vib, 3=trem, 4=vslide bool fxUsage[DIV_MAX_CHANS][5]; SafeReader reader=SafeReader(file,len); warnings=""; memset(defaultVols,0,31*sizeof(short)); memset(sampLens,0,31*sizeof(int)); memset(fxUsage,0,DIV_MAX_CHANS*5*sizeof(bool)); try { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_MOD; ds.linearPitch=0; ds.noSlidesOnFirstTick=true; ds.rowResetsArpPos=true; ds.ignoreJumpAtEnd=false; ds.delayBehavior=0; int insCount=31; bool bypassLimits=false; // check mod magic bytes if (!reader.seek(1080,SEEK_SET)) { logD("couldn't seek to 1080"); throw EndOfFileException(&reader,reader.tell()); } if (reader.read(magic,4)!=4) { logD("the magic isn't complete"); throw EndOfFileException(&reader,reader.tell()); } if (memcmp(magic,"M.K.",4)==0 || memcmp(magic,"M!K!",4)==0 || memcmp(magic,"M&K!",4)==0) { logD("detected a ProTracker module"); ds.systemName="Amiga"; chCount=4; } else if (memcmp(magic,"CD81",4)==0 || memcmp(magic,"OKTA",4)==0 || memcmp(magic,"OCTA",4)==0) { logD("detected an Oktalyzer/Octalyzer/OctaMED module"); ds.systemName="Amiga (8-channel)"; chCount=8; } else if (memcmp(magic+1,"CHN",3)==0 && magic[0]>='1' && magic[0]<='9') { logD("detected a FastTracker module"); ds.systemName="PC"; chCount=magic[0]-'0'; } else if (memcmp(magic,"FLT",3)==0 && magic[3]>='1' && magic[3]<='9') { logD("detected a Fairlight module"); ds.systemName="Amiga"; chCount=magic[3]-'0'; } else if (memcmp(magic,"TDZ",3)==0 && magic[3]>='1' && magic[3]<='9') { logD("detected a TakeTracker module"); ds.systemName="PC"; chCount=magic[3]-'0'; } else if ((memcmp(magic+2,"CH",2)==0 || memcmp(magic+2,"CN",2)==0) && (magic[0]>='1' && magic[0]<='9' && magic[1]>='0' && magic[1]<='9')) { logD("detected a Fast/TakeTracker module"); ds.systemName="PC"; chCount=((magic[0]-'0')*10)+(magic[1]-'0'); } else { insCount=15; logD("possibly a Soundtracker module"); ds.systemName="Amiga"; chCount=4; } // song name if (!reader.seek(0,SEEK_SET)) { logD("couldn't seek to 0"); throw EndOfFileException(&reader,reader.tell()); } ds.name=reader.readString(20); logI("%s",ds.name); // samples logD("reading samples... (%d)",insCount); for (int i=0; idepth=DIV_SAMPLE_DEPTH_8BIT; sample->name=reader.readString(22); logD("%d: %s",i+1,sample->name); int slen=((unsigned short)reader.readS_BE())*2; sampLens[i]=slen; if (slen==2) slen=0; signed char fineTune=reader.readC()&0x0f; if (fineTune>=8) fineTune-=16; sample->rate=(int)(pow(2.0,(double)fineTune/96.0)*8363.0); sample->centerRate=sample->rate; defaultVols[i]=reader.readC(); int loopStart=reader.readS_BE()*2; int loopLen=reader.readS_BE()*2; int loopEnd=loopStart+loopLen; // bunch of checks since ProTracker abuses those for one-shot samples if (loopStart>loopEnd || loopEnd<4 || loopLen<4) { loopStart=0; loopLen=0; } if (loopLen>=2) { sample->loopStart=loopStart; sample->loopEnd=loopEnd; sample->loop=(sample->loopStart>=0)&&(sample->loopEnd>=0); } sample->init(slen); ds.sample.push_back(sample); } ds.sampleLen=ds.sample.size(); // orders ds.subsong[0]->ordersLen=ordCount=reader.readC(); if (ds.subsong[0]->ordersLen<1 || ds.subsong[0]->ordersLen>127) { logD("invalid order count!"); throw EndOfFileException(&reader,reader.tell()); } unsigned char restartPos=reader.readC(); // restart position, unused logD("restart position byte: %.2x",restartPos); if (insCount==15) { if (restartPos>0x60 && restartPos<0x80) { logD("detected a Soundtracker module"); } else { logD("no Soundtracker signature found"); throw EndOfFileException(&reader,reader.tell()); } } int patMax=0; for (int i=0; i<128; i++) { unsigned char pat=reader.readC(); if (pat>patMax) patMax=pat; for (int j=0; jorders.ord[j][i]=pat; } } if (insCount==15) { if (!reader.seek(600,SEEK_SET)) { logD("couldn't seek to 600"); throw EndOfFileException(&reader,reader.tell()); } } else { if (!reader.seek(1084,SEEK_SET)) { logD("couldn't seek to 1084"); throw EndOfFileException(&reader,reader.tell()); } } // patterns ds.subsong[0]->patLen=64; for (int ch=0; chpat[ch].getPattern(pat,true); } for (int row=0; row<64; row++) { for (int ch=0; chdata[row]; unsigned char data[4]; reader.read(&data,4); // instrument short ins=(data[0]&0xf0)|(data[2]>>4); if (ins>0) { dstrow[2]=ins-1; dstrow[3]=defaultVols[ins-1]; } // note int period=data[1]+((data[0]&0x0f)<<8); if (period>0 && period<0x0fff) { short note=(short)round(log2(3424.0/period)*12); dstrow[0]=((note-1)%12)+1; dstrow[1]=(note-1)/12+1; if (period<114) { bypassLimits=true; } } // effects are done later short fxtyp=data[2]&0x0f; short fxval=data[3]; dstrow[4]=fxtyp; dstrow[5]=fxval; switch (fxtyp) { case 0: if (fxval!=0) fxUsage[ch][0]=true; break; case 1: case 2: case 3: fxUsage[ch][1]=true; break; case 4: fxUsage[ch][2]=true; break; case 5: fxUsage[ch][1]=true; fxUsage[ch][4]=true; break; case 6: fxUsage[ch][2]=true; fxUsage[ch][4]=true; break; case 7: fxUsage[ch][3]=true; break; case 10: if (fxval!=0) fxUsage[ch][4]=true; break; } } } } // samples size_t pos=reader.tell(); logD("reading sample data..."); for (int i=0; isamples,sampLens[i]); if (!reader.seek(pos,SEEK_SET)) { logD("%d: couldn't seek to %d",i,pos); throw EndOfFileException(&reader,reader.tell()); } reader.read(ds.sample[i]->data8,ds.sample[i]->samples); pos+=sampLens[i]; } // convert effects logD("converting module..."); for (int ch=0; ch<=chCount; ch++) { unsigned char fxCols=1; for (int pat=0; pat<=patMax; pat++) { auto* data=ds.subsong[0]->pat[ch].getPattern(pat,true)->data; short lastPitchEffect=-1; short lastEffectState[5]={-1,-1,-1,-1,-1}; short setEffectState[5]={-1,-1,-1,-1,-1}; for (int row=0;row<64;row++) { const short fxUsageTyp[5]={0x00,0x01,0x04,0x07,0xFA}; short effectState[5]={0,0,0,0,0}; unsigned char curFxCol=0; short fxTyp=data[row][4]; short fxVal=data[row][5]; auto writeFxCol=[data,row,&curFxCol](short typ, short val) { data[row][4+curFxCol*2]=typ; data[row][5+curFxCol*2]=val; curFxCol++; }; writeFxCol(-1,-1); curFxCol=0; switch (fxTyp) { case 0: // arp effectState[0]=fxVal; break; case 5: // vol slide + porta effectState[4]=fxVal; fxTyp=3; fxVal=0; // fall through case 1: // note slide up case 2: // note slide down case 3: // porta if (fxTyp==3 && fxVal==0) { if (setEffectState[1]<0) break; fxVal=setEffectState[1]; } setEffectState[1]=fxVal; effectState[1]=fxVal; if ((effectState[1]!=lastEffectState[1]) || (fxTyp!=lastPitchEffect) || (effectState[1]!=0 && data[row][0]>0)) { writeFxCol(fxTyp,fxVal); } lastPitchEffect=fxTyp; lastEffectState[1]=fxVal; break; case 6: // vol slide + vibrato effectState[4]=fxVal; fxTyp=4; fxVal=0; // fall through case 4: // vibrato // TODO: handle 0 value? if (fxVal==0) { if (setEffectState[2]<0) break; fxVal=setEffectState[2]; } effectState[2]=fxVal; setEffectState[2]=fxVal; break; case 7: // tremolo if (fxVal==0) { if (setEffectState[3]<0) break; fxVal=setEffectState[3]; } effectState[3]=fxVal; setEffectState[3]=fxVal; break; case 9: // set offset writeFxCol(0x90,fxVal); break; case 10: // vol slide effectState[4]=fxVal; break; case 11: // jump to pos writeFxCol(fxTyp,fxVal); break; case 12: // set vol data[row][3]=MIN(0x40,fxVal); break; case 13: // break to row (BCD) writeFxCol(fxTyp,((fxVal>>4)*10)+(fxVal&15)); break; case 15: // set speed // TODO: somehow handle VBlank tunes // TODO: i am so sorry if (fxVal>0x20 && ds.name!="klisje paa klisje") { writeFxCol(0xf0,fxVal); } else { writeFxCol(0x0f,fxVal); } break; case 14: // extended fxTyp=fxVal>>4; fxVal&=0x0f; switch (fxTyp) { case 0: writeFxCol(0x10,!fxVal); break; case 1: // single note slide up case 2: // single note slide down writeFxCol(fxTyp-1+0xf1,fxVal); break; case 9: // retrigger writeFxCol(0x0c,fxVal); break; case 10: // single vol slide up case 11: // single vol slide down writeFxCol(fxTyp-10+0xf8,fxVal); break; case 12: // note cut case 13: // note delay writeFxCol(fxTyp-12+0xec,fxVal); break; } break; } for (int i=0; i<5; i++) { // pitch slide and volume slide needs to be kept active on new note // even after target/max is reached if (fxUsage[ch][i] && (effectState[i]!=lastEffectState[i] || (effectState[i]!=0 && i==4 && data[row][3]>=0))) { writeFxCol(fxUsageTyp[i],effectState[i]); } } memcpy(lastEffectState,effectState,sizeof(effectState)); if (curFxCol>fxCols) { fxCols=curFxCol; } } } ds.subsong[0]->pat[ch].effectCols=fxCols; } ds.subsong[0]->hz=50; ds.systemLen=(chCount+3)/4; for(int i=0; i1 || bypassLimits)); } for(int i=0; ichanShow[i]=true; ds.subsong[0]->chanName[i]=fmt::sprintf("Channel %d",i+1); ds.subsong[0]->chanShortName[i]=fmt::sprintf("C%d",i+1); } for(int i=chCount; ipat[i].effectCols=1; ds.subsong[0]->chanShow[i]=false; } // instrument creation for(int i=0; itype=DIV_INS_AMIGA; ins->amiga.initSample=i; ins->name=ds.sample[i]->name; ds.ins.push_back(ins); } ds.insLen=ds.ins.size(); if (active) quitDispatch(); BUSY_BEGIN_SOFT; saveLock.lock(); song.unload(); song=ds; changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; if (active) { initDispatch(); BUSY_BEGIN; renderSamples(); reset(); BUSY_END; } success=true; } catch (EndOfFileException& e) { //logE("premature end of file!"); lastError="incomplete file"; } catch (InvalidHeaderException& e) { //logE("invalid info header!"); lastError="invalid info header!"; } return success; } unsigned char fcXORTriangle[32]={ 0xc0, 0xc0, 0xd0, 0xd8, 0xe0, 0xe8, 0xf0, 0xf8, 0x00, 0xf8, 0xf0, 0xe8, 0xe0, 0xd8, 0xd0, 0xc8, 0xc0, 0xb8, 0xb0, 0xa8, 0xa0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8 }; unsigned char fcCustom1[32]={ 0x45, 0x45, 0x79, 0x7d, 0x7a, 0x77, 0x70, 0x66, 0x61, 0x58, 0x53, 0x4d, 0x2c, 0x20, 0x18, 0x12, 0x04, 0xdb, 0xd3, 0xcd, 0xc6, 0xbc, 0xb5, 0xae, 0xa8, 0xa3, 0x9d, 0x99, 0x93, 0x8e, 0x8b, 0x8a }; unsigned char fcCustom2[32]={ 0x45, 0x45, 0x79, 0x7d, 0x7a, 0x77, 0x70, 0x66, 0x5b, 0x4b, 0x43, 0x37, 0x2c, 0x20, 0x18, 0x12, 0x04, 0xf8, 0xe8, 0xdb, 0xcf, 0xc6, 0xbe, 0xb0, 0xa8, 0xa4, 0x9e, 0x9a, 0x95, 0x94, 0x8d, 0x83 }; unsigned char fcTinyTriangle[16]={ 0x00, 0x00, 0x40, 0x60, 0x7f, 0x60, 0x40, 0x20, 0x00, 0xe0, 0xc0, 0xa0, 0x80, 0xa0, 0xc0, 0xe0 }; void generateFCPresetWave(int index, DivWavetable* wave) { wave->max=255; wave->len=32; switch (index) { case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: // XOR triangle for (int i=0; i<32; i++) { wave->data[i]=(unsigned char)((fcXORTriangle[i]^0x80)^(((index+15)data[i]=(index>i)?0x01:0xff; } break; case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: // tiny pulse for (int i=0; i<32; i++) { wave->data[i]=((index-0x18)>(i&15))?0x01:0xff; } break; case 0x28: case 0x2e: // saw for (int i=0; i<32; i++) { wave->data[i]=i<<3; } break; case 0x29: case 0x2f: // tiny saw for (int i=0; i<32; i++) { wave->data[i]=(i<<4)&0xff; } break; case 0x2a: // custom 1 for (int i=0; i<32; i++) { wave->data[i]=fcCustom1[i]^0x80; } break; case 0x2b: // custom 2 for (int i=0; i<32; i++) { wave->data[i]=fcCustom2[i]^0x80; } break; case 0x2c: case 0x2d: // tiny triangle for (int i=0; i<32; i++) { wave->data[i]=fcTinyTriangle[i&15]^0x80; } break; default: for (int i=0; i<32; i++) { wave->data[i]=i; } break; } } bool DivEngine::loadS3M(unsigned char* file, size_t len) { struct InvalidHeaderException {}; bool success=false; char magic[4]={0,0,0,0}; SafeReader reader=SafeReader(file,len); warnings=""; unsigned char chanSettings[32]; unsigned char ord[256]; unsigned short insPtr[256]; unsigned short patPtr[256]; unsigned char chanPan[16]; unsigned char defVol[256]; try { DivSong ds; ds.version=DIV_VERSION_S3M; ds.linearPitch=0; ds.pitchMacroIsLinear=false; ds.noSlidesOnFirstTick=true; ds.rowResetsArpPos=true; ds.ignoreJumpAtEnd=false; // load here if (!reader.seek(0x2c,SEEK_SET)) { logE("premature end of file!"); lastError="incomplete file"; delete[] file; return false; } reader.read(magic,4); if (memcmp(magic,DIV_S3M_MAGIC,4)!=0) { logW("the magic isn't complete"); throw EndOfFileException(&reader,reader.tell()); } if (!reader.seek(0,SEEK_SET)) { logE("premature end of file!"); lastError="incomplete file"; delete[] file; return false; } ds.name=reader.readString(28); reader.readC(); // 0x1a if (reader.readC()!=16) { logW("type is wrong!"); } reader.readS(); // x unsigned short ordersLen=reader.readS(); ds.insLen=reader.readS(); if (ds.insLen<0 || ds.insLen>256) { logE("invalid instrument count!"); lastError="invalid instrument count!"; delete[] file; return false; } unsigned short patCount=reader.readS(); unsigned short flags=reader.readS(); unsigned short version=reader.readS(); bool signedSamples=(reader.readS()==1); if ((flags&64) || version==0x1300) { ds.noSlidesOnFirstTick=false; } reader.readI(); // "SCRM" unsigned char globalVol=reader.readC(); ds.subsong[0]->speeds.val[0]=(unsigned char)reader.readC(); ds.subsong[0]->hz=((double)reader.readC())/2.5; unsigned char masterVol=reader.readC(); logV("masterVol: %d",masterVol); logV("signedSamples: %d",signedSamples); logV("globalVol: %d",globalVol); reader.readC(); // UC bool defaultPan=(((unsigned char)reader.readC())==252); reader.readS(); // reserved reader.readI(); reader.readI(); // the last 2 bytes is Special. we don't read that. reader.read(chanSettings,32); logD("reading orders..."); for (int i=0; i=32) continue; if ((chanSettings[i]&127)>=16) { hasFM=true; } else { hasPCM=true; } if (hasFM && hasPCM) break; } ds.systemName="PC"; if (hasPCM) { ds.system[ds.systemLen]=DIV_SYSTEM_ES5506; ds.systemVol[ds.systemLen]=1.0f; ds.systemPan[ds.systemLen]=0; ds.systemLen++; } if (hasFM) { ds.system[ds.systemLen]=DIV_SYSTEM_OPL2; ds.systemVol[ds.systemLen]=1.0f; ds.systemPan[ds.systemLen]=0; ds.systemLen++; } // load instruments/samples for (int i=0; itype=DIV_INS_ES5506; } else if (memcmp(magic,"SCRI",4)==0) { ins->type=DIV_INS_OPL; } else { ins->type=DIV_INS_ES5506; ds.ins.push_back(ins); continue; } if (!reader.seek(insPtr[i]*16,SEEK_SET)) { logE("premature end of file!"); lastError="incomplete file"; delete ins; delete[] file; return false; } String dosName=reader.readString(13); if (ins->type==DIV_INS_ES5506) { unsigned int memSeg=0; memSeg=(unsigned char)reader.readC(); memSeg|=((unsigned short)reader.readS())<<8; logV("memSeg: %d",memSeg); unsigned int length=reader.readI(); DivSample* s=new DivSample; s->depth=DIV_SAMPLE_DEPTH_8BIT; s->init(length); s->loopStart=reader.readI(); s->loopEnd=reader.readI(); defVol[i]=reader.readC(); logV("defVol: %d",defVol[i]); reader.readC(); // x } else { } ds.ins.push_back(ins); } if (active) quitDispatch(); BUSY_BEGIN_SOFT; saveLock.lock(); song.unload(); song=ds; changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; if (active) { initDispatch(); BUSY_BEGIN; renderSamples(); reset(); BUSY_END; } success=true; } catch (EndOfFileException& e) { //logE("premature end of file!"); lastError="incomplete file"; } catch (InvalidHeaderException& e) { //logE("invalid header!"); lastError="invalid header!"; } return success; } bool DivEngine::loadFC(unsigned char* file, size_t len) { struct InvalidHeaderException {}; bool success=false; char magic[4]={0,0,0,0}; SafeReader reader=SafeReader(file,len); warnings=""; bool isFC14=false; unsigned int patPtr, freqMacroPtr, volMacroPtr, samplePtr, wavePtr; unsigned int seqLen, patLen, freqMacroLen, volMacroLen, sampleLen; unsigned char waveLen[80]; //unsigned char waveLoopLen[40]; struct FCSequence { unsigned char pat[4]; signed char transpose[4]; signed char offsetIns[4]; unsigned char speed; }; std::vector seq; struct FCPattern { unsigned char note[32]; unsigned char val[32]; }; std::vector pat; struct FCMacro { unsigned char val[64]; }; std::vector freqMacros; std::vector volMacros; struct FCSample { unsigned short loopLen, len, loopStart; } sample[10]; try { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_FC; //ds.linearPitch=0; //ds.pitchMacroIsLinear=false; //ds.noSlidesOnFirstTick=true; //ds.rowResetsArpPos=true; ds.pitchSlideSpeed=8; ds.ignoreJumpAtEnd=false; // load here if (!reader.seek(0,SEEK_SET)) { logE("premature end of file!"); lastError="incomplete file"; delete[] file; return false; } reader.read(magic,4); if (memcmp(magic,DIV_FC13_MAGIC,4)==0) { isFC14=false; } else if (memcmp(magic,DIV_FC14_MAGIC,4)==0) { isFC14=true; } else { logW("the magic isn't complete"); throw EndOfFileException(&reader,reader.tell()); } ds.systemLen=1; ds.system[0]=DIV_SYSTEM_AMIGA; ds.systemVol[0]=1.0f; ds.systemPan[0]=0; ds.systemFlags[0].set("clockSel",1); // PAL ds.systemFlags[0].set("stereoSep",80); ds.systemName="Amiga"; seqLen=reader.readI_BE(); if (seqLen%13) { logW("sequence length is not multiple of 13 (%d)",seqLen); //throw EndOfFileException(&reader,reader.tell()); } patPtr=reader.readI_BE(); patLen=reader.readI_BE(); if (patLen%64) { logW("pattern length is not multiple of 64 (%d)",patLen); throw EndOfFileException(&reader,reader.tell()); } freqMacroPtr=reader.readI_BE(); freqMacroLen=reader.readI_BE(); if (freqMacroLen%64) { logW("freq sequence length is not multiple of 64 (%d)",freqMacroLen); //throw EndOfFileException(&reader,reader.tell()); } volMacroPtr=reader.readI_BE(); volMacroLen=reader.readI_BE(); if (volMacroLen%64) { logW("vol sequence length is not multiple of 64 (%d)",volMacroLen); //throw EndOfFileException(&reader,reader.tell()); } samplePtr=reader.readI_BE(); if (isFC14) { wavePtr=reader.readI_BE(); // wave len sampleLen=0; } else { sampleLen=reader.readI_BE(); wavePtr=0; } logD("patPtr: %x",patPtr); logD("patLen: %d",patLen); logD("freqMacroPtr: %x",freqMacroPtr); logD("freqMacroLen: %d",freqMacroLen); logD("volMacroPtr: %x",volMacroPtr); logD("volMacroLen: %d",volMacroLen); logD("samplePtr: %x",samplePtr); if (isFC14) { logD("wavePtr: %x",wavePtr); } else { logD("sampleLen: %d",sampleLen); } // sample info logD("samples: (%x)",reader.tell()); for (int i=0; i<10; i++) { sample[i].len=reader.readS_BE(); sample[i].loopStart=reader.readS_BE(); sample[i].loopLen=reader.readS_BE(); logD("- %d: %d (%d, %d)",i,sample[i].len,sample[i].loopStart,sample[i].loopLen); } // wavetable lengths if (isFC14) { logD("wavetables:"); for (int i=0; i<80; i++) { waveLen[i]=(unsigned char)reader.readC(); logD("- %d: %.4x",i,waveLen[i]); } } // sequences seqLen/=13; logD("reading sequences... (%d)",seqLen); for (unsigned int i=0; idepth=DIV_SAMPLE_DEPTH_8BIT; if (sample[i].len>0) { s->init(sample[i].len*2); } s->name=fmt::sprintf("Sample %d",i+1); if (sample[i].loopLen>1) { s->loopStart=sample[i].loopStart; s->loopEnd=sample[i].loopStart+(sample[i].loopLen*2); s->loop=(s->loopStart>=0)&&(s->loopEnd>=0); } reader.read(s->data8,sample[i].len*2); ds.sample.push_back(s); } ds.sampleLen=(int)ds.sample.size(); // wavetables if (isFC14) { if (!reader.seek(wavePtr,SEEK_SET)) { logE("premature end of file!"); lastError="incomplete file"; delete[] file; return false; } logD("reading wavetables..."); for (int i=0; i<80; i++) { DivWavetable* w=new DivWavetable; w->min=0; w->max=255; w->len=MIN(256,waveLen[i]*2); for (int i=0; i<256; i++) { w->data[i]=128; } if (waveLen[i]>0) { signed char* waveArray=new signed char[waveLen[i]*2]; reader.read(waveArray,waveLen[i]*2); int howMany=waveLen[i]*2; if (howMany>256) howMany=256; for (int i=0; idata[i]=waveArray[i]+128; } delete[] waveArray; } else { logV("empty wave %d",i); generateFCPresetWave(i,w); } ds.wave.push_back(w); } } else { // generate preset waves for (int i=0; i<48; i++) { DivWavetable* w=new DivWavetable; generateFCPresetWave(i,w); ds.wave.push_back(w); } } ds.waveLen=(int)ds.wave.size(); // convert ds.subsong[0]->ordersLen=seqLen; ds.subsong[0]->patLen=32; ds.subsong[0]->hz=50; ds.subsong[0]->pat[3].effectCols=3; ds.subsong[0]->speeds.val[0]=3; ds.subsong[0]->speeds.len=1; int lastIns[4]; int lastNote[4]; signed char lastTranspose[4]; bool isSliding[4]; memset(lastIns,-1,4*sizeof(int)); memset(lastNote,-1,4*sizeof(int)); memset(lastTranspose,0,4); memset(isSliding,0,4*sizeof(bool)); for (unsigned int i=0; iorders.ord[j][i]=i; DivPattern* p=ds.subsong[0]->pat[j].getPattern(i,true); if (j==3 && seq[i].speed) { p->data[0][6]=0x0f; p->data[0][7]=seq[i].speed; } bool ignoreNext=false; for (int k=0; k<32; k++) { FCPattern& fp=pat[seq[i].pat[j]]; if (fp.note[k]>0 && fp.note[k]<0x49) { lastNote[j]=fp.note[k]; short note=(fp.note[k]+seq[i].transpose[j])%12; short octave=2+((fp.note[k]+seq[i].transpose[j])/12); if (fp.note[k]>=0x3d) octave-=6; if (note==0) { note=12; octave--; } octave&=0xff; p->data[k][0]=note; p->data[k][1]=octave; if (isSliding[j]) { isSliding[j]=false; p->data[k][4]=2; p->data[k][5]=0; } } else if (fp.note[k]==0x49) { if (k>0) { p->data[k-1][4]=0x0d; p->data[k-1][5]=0; } } else if (k==0 && lastTranspose[j]!=seq[i].transpose[j]) { p->data[0][2]=lastIns[j]; p->data[0][4]=0x03; p->data[0][5]=0xff; lastTranspose[j]=seq[i].transpose[j]; short note=(lastNote[j]+seq[i].transpose[j])%12; short octave=2+((lastNote[j]+seq[i].transpose[j])/12); if (lastNote[j]>=0x3d) octave-=6; if (note==0) { note=12; octave--; } octave&=0xff; p->data[k][0]=note; p->data[k][1]=octave; } if (fp.val[k]) { if (ignoreNext) { ignoreNext=false; } else { if (fp.val[k]==0xf0) { p->data[k][0]=100; p->data[k][1]=0; p->data[k][2]=-1; } else if (fp.val[k]&0xe0) { if (fp.val[k]&0x40) { p->data[k][4]=2; p->data[k][5]=0; isSliding[j]=false; } else if (fp.val[k]&0x80) { isSliding[j]=true; if (k<31) { if (fp.val[k+1]&0x20) { p->data[k][4]=2; p->data[k][5]=fp.val[k+1]&0x1f; } else { p->data[k][4]=1; p->data[k][5]=fp.val[k+1]&0x1f; } ignoreNext=true; } else { p->data[k][4]=2; p->data[k][5]=0; } } } else { p->data[k][2]=(fp.val[k]+seq[i].offsetIns[j])&0x3f; lastIns[j]=p->data[k][2]; } } } else if (fp.note[k]>0 && fp.note[k]<0x49) { p->data[k][2]=seq[i].offsetIns[j]; lastIns[j]=p->data[k][2]; } } } } // convert instruments for (unsigned int i=0; itype=DIV_INS_AMIGA; ins->name=fmt::sprintf("Instrument %d",i); ins->amiga.useWave=true; unsigned char seqSpeed=m.val[0]; unsigned char freqMacro=m.val[1]; unsigned char vibSpeed=m.val[2]; unsigned char vibDepth=m.val[3]; unsigned char vibDelay=m.val[4]; unsigned char lastVal=m.val[5]; signed char loopMap[64]; memset(loopMap,-1,64); signed char loopMapFreq[64]; memset(loopMapFreq,-1,64); signed char loopMapWave[64]; memset(loopMapWave,-1,64); // volume sequence ins->std.volMacro.len=0; for (int j=5; j<64; j++) { loopMap[j]=ins->std.volMacro.len; if (m.val[j]==0xe1) { // end break; } else if (m.val[j]==0xe0) { // loop if (++j>=64) break; ins->std.volMacro.loop=loopMap[m.val[j]&63]; break; } else if (m.val[j]==0xe8) { // sustain if (++j>=64) break; unsigned char susTime=m.val[j]; // TODO: <= or std.volMacro.val[ins->std.volMacro.len]=lastVal; if (++ins->std.volMacro.len>=255) break; } if (ins->std.volMacro.len>=255) break; } else if (m.val[j]==0xe9 || m.val[j]==0xea) { // volume slide if (++j>=64) break; signed char slideStep=m.val[j]; if (++j>=64) break; unsigned char slideTime=m.val[j]; // TODO: <= or 0) { lastVal+=slideStep; if (lastVal>63) lastVal=63; } else { if (-slideStep>lastVal) { lastVal=0; } else { lastVal-=slideStep; } } ins->std.volMacro.val[ins->std.volMacro.len]=lastVal; if (++ins->std.volMacro.len>=255) break; } } else { // TODO: replace with upcoming macro speed for (int k=0; kstd.volMacro.val[ins->std.volMacro.len]=m.val[j]; lastVal=m.val[j]; if (++ins->std.volMacro.len>=255) break; } if (ins->std.volMacro.len>=255) break; } } // frequency sequence lastVal=0; ins->amiga.initSample=-1; if (freqMacrostd.arpMacro.len; loopMapWave[j]=ins->std.waveMacro.len; if (fm.val[j]==0xe1) { break; } else if (fm.val[j]==0xe2 || fm.val[j]==0xe4) { if (++j>=64) break; unsigned char wave=fm.val[j]; if (wave<10) { // sample if (ins->amiga.initSample==-1) { ins->amiga.initSample=wave; ins->amiga.useWave=false; } } else { // waveform ins->std.waveMacro.val[ins->std.waveMacro.len]=wave-10; ins->std.waveMacro.open=true; lastVal=wave; //if (++ins->std.arpMacro.len>=255) break; } } else if (fm.val[j]==0xe0) { if (++j>=64) break; ins->std.arpMacro.loop=loopMapFreq[fm.val[j]&63]; ins->std.waveMacro.loop=loopMapWave[fm.val[j]&63]; break; } else if (fm.val[j]==0xe3) { logV("unhandled vibrato!"); } else if (fm.val[j]==0xe8) { logV("unhandled sustain!"); } else if (fm.val[j]==0xe7) { if (++j>=64) break; fm=freqMacros[MIN(fm.val[j],freqMacros.size()-1)]; j=0; } else if (fm.val[j]==0xe9) { logV("unhandled pack!"); } else if (fm.val[j]==0xea) { logV("unhandled pitch!"); } else { if (fm.val[j]>0x80) { ins->std.arpMacro.val[ins->std.arpMacro.len]=(fm.val[j]-0x80+24)^0x40000000; } else { ins->std.arpMacro.val[ins->std.arpMacro.len]=fm.val[j]; } if (lastVal>=10) { ins->std.waveMacro.val[ins->std.waveMacro.len]=lastVal-10; } ins->std.arpMacro.open=true; if (++ins->std.arpMacro.len>=255) break; if (++ins->std.waveMacro.len>=255) break; } } } // waveform width if (lastVal>=10 && (unsigned int)(lastVal-10)amiga.waveLen=ds.wave[lastVal-10]->len-1; } // vibrato for (int j=0; j<=vibDelay; j++) { ins->std.pitchMacro.val[ins->std.pitchMacro.len]=0; if (++ins->std.pitchMacro.len>=255) break; } int vibPos=0; ins->std.pitchMacro.loop=ins->std.pitchMacro.len; do { vibPos+=vibSpeed; if (vibPos>vibDepth) vibPos=vibDepth; ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*32; if (++ins->std.pitchMacro.len>=255) break; } while (vibPosstd.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*32; if (++ins->std.pitchMacro.len>=255) break; } while (vibPos>-vibDepth); do { vibPos+=vibSpeed; if (vibPos>0) vibPos=0; ins->std.pitchMacro.val[ins->std.pitchMacro.len]=vibPos*32; if (++ins->std.pitchMacro.len>=255) break; } while (vibPos<0); ds.ins.push_back(ins); } ds.insLen=(int)ds.ins.size(); // optimize ds.subsong[0]->optimizePatterns(); ds.subsong[0]->rearrangePatterns(); if (active) quitDispatch(); BUSY_BEGIN_SOFT; saveLock.lock(); song.unload(); song=ds; changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; if (active) { initDispatch(); BUSY_BEGIN; renderSamples(); reset(); BUSY_END; } success=true; } catch (EndOfFileException& e) { //logE("premature end of file!"); lastError="incomplete file"; } catch (InvalidHeaderException& e) { //logE("invalid header!"); lastError="invalid header!"; } return success; } #define CHECK_BLOCK_VERSION(x) \ if (blockVersion>x) { \ logW("incompatible block version %d for %s!",blockVersion,blockName); \ } 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, 0xe2, 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 }; constexpr int ftEffectMapSize=sizeof(ftEffectMap)/sizeof(int); bool DivEngine::loadFTM(unsigned char* file, size_t len) { SafeReader reader=SafeReader(file,len); warnings=""; try { DivSong ds; String blockName; unsigned char 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); if (!reader.seek(18,SEEK_SET)) { logE("premature end of file!"); lastError="incomplete file"; delete[] file; return false; } 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"; delete[] file; return false; } for (DivSubSong* i: ds.subsong) { i->clearData(); delete i; } ds.subsong.clear(); ds.linearPitch=0; while (true) { 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") { // versions 7-9 don't change anything? CHECK_BLOCK_VERSION(9); unsigned int oldSpeedTempo=0; if (blockVersion<=1) { oldSpeedTempo=reader.readI(); } if (blockVersion>=2) { 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; } } 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(); } if (blockVersion>=8) { int fineTuneCents=reader.readC()*100; fineTuneCents+=reader.readC(); ds.tuning=440.0*pow(2.0,(double)fineTuneCents/1200.0); } 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); // 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; 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); 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; } logV("- %s",subSongName); } for (unsigned int i=0; ipat[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(6); 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=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; break; case 2: // TODO: tell VRC6 and VRC6 saw instruments apart ins->type=DIV_INS_VRC6; break; case 3: ins->type=DIV_INS_OPLL; break; case 4: ins->type=DIV_INS_FDS; break; case 5: ins->type=DIV_INS_N163; break; case 6: // 5B? ins->type=DIV_INS_AY; break; default: { logE("%d: invalid instrument type %d",insIndex,insType); lastError="invalid instrument type"; delete[] file; return false; } } // 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"; 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 } } break; } case DIV_INS_VRC6: { unsigned int totalSeqs=reader.readI(); if (totalSeqs>4) { 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 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(); } 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(); reader.readI(); // this is delay. currently ignored. TODO. ds.wave.push_back(wave); 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(); } 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(); } 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(); } break; } case DIV_INS_N163: { // TODO! break; } // TODO: 5B! default: { 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); } */ } else if (blockName=="SEQUENCES") { CHECK_BLOCK_VERSION(6); reader.seek(blockSize,SEEK_CUR); } else if (blockName=="FRAMES") { CHECK_BLOCK_VERSION(3); 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); for (int j=0; jordersLen; j++) { for (int k=0; korders.ord[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()=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; } 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? CHECK_BLOCK_VERSION(6); reader.seek(blockSize,SEEK_CUR); } else if (blockName=="SEQUENCES_N163") { CHECK_BLOCK_VERSION(1); reader.seek(blockSize,SEEK_CUR); } else if (blockName=="COMMENTS") { CHECK_BLOCK_VERSION(1); reader.seek(blockSize,SEEK_CUR); } else { 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; delete[] file; return false; } } addWarning("FamiTracker import is experimental!"); ds.version=DIV_VERSION_FTM; if (active) quitDispatch(); BUSY_BEGIN_SOFT; saveLock.lock(); song.unload(); song=ds; changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; if (active) { initDispatch(); BUSY_BEGIN; renderSamples(); reset(); BUSY_END; } } catch (EndOfFileException& e) { logE("premature end of file!"); lastError="incomplete file"; delete[] file; return false; } delete[] file; return true; } bool DivEngine::load(unsigned char* f, size_t slen) { unsigned char* file; size_t len; if (slen<18) { logE("too small!"); lastError="file is too small"; delete[] f; return false; } if (!systemsRegistered) registerSystems(); // step 1: try loading as a zlib-compressed file logD("trying zlib..."); try { z_stream zl; memset(&zl,0,sizeof(z_stream)); zl.avail_in=slen; zl.next_in=(Bytef*)f; zl.zalloc=NULL; zl.zfree=NULL; zl.opaque=NULL; int nextErr; nextErr=inflateInit(&zl); if (nextErr!=Z_OK) { if (zl.msg==NULL) { logD("zlib error: unknown! %d",nextErr); } else { logD("zlib error: %s",zl.msg); } inflateEnd(&zl); lastError="not a .dmf/.fur song"; throw NotZlibException(0); } std::vector blocks; while (true) { InflateBlock* ib=new InflateBlock(DIV_READ_SIZE); zl.next_out=ib->buf; zl.avail_out=ib->len; nextErr=inflate(&zl,Z_SYNC_FLUSH); if (nextErr!=Z_OK && nextErr!=Z_STREAM_END) { if (zl.msg==NULL) { logD("zlib error: unknown error! %d",nextErr); lastError="unknown decompression error"; } else { logD("zlib inflate: %s",zl.msg); lastError=fmt::sprintf("decompression error: %s",zl.msg); } for (InflateBlock* i: blocks) delete i; blocks.clear(); delete ib; inflateEnd(&zl); throw NotZlibException(0); } ib->blockSize=ib->len-zl.avail_out; blocks.push_back(ib); if (nextErr==Z_STREAM_END) { break; } } nextErr=inflateEnd(&zl); if (nextErr!=Z_OK) { if (zl.msg==NULL) { logD("zlib end error: unknown error! %d",nextErr); lastError="unknown decompression finish error"; } else { logD("zlib end: %s",zl.msg); lastError=fmt::sprintf("decompression finish error: %s",zl.msg); } for (InflateBlock* i: blocks) delete i; blocks.clear(); throw NotZlibException(0); } size_t finalSize=0; size_t curSeek=0; for (InflateBlock* i: blocks) { finalSize+=i->blockSize; } if (finalSize<1) { logD("compressed too small!"); lastError="file too small"; for (InflateBlock* i: blocks) delete i; blocks.clear(); throw NotZlibException(0); } file=new unsigned char[finalSize]; for (InflateBlock* i: blocks) { memcpy(&file[curSeek],i->buf,i->blockSize); curSeek+=i->blockSize; delete i; } blocks.clear(); len=finalSize; delete[] f; } catch (NotZlibException& e) { logD("not zlib. loading as raw..."); file=f; len=slen; } // step 2: try loading as .fur or .dmf 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); } else if (memcmp(file,DIV_FUR_MAGIC,16)==0) { return loadFur(file,len); } else if (memcmp(file,DIV_FC13_MAGIC,4)==0 || memcmp(file,DIV_FC14_MAGIC,4)==0) { return loadFC(file,len); } // step 3: try loading as .mod if (loadMod(f,slen)) { delete[] f; return true; } // step 4: not a valid file logE("not a valid module!"); lastError="not a compatible song"; delete[] file; return false; } 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(); for (unsigned 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]); } 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); for (int i=0; i<7; 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 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]); } 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 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 for (int i=0; itell()); ins->putInsData2(w,false); } /// WAVETABLE for (int i=0; itell()); wave->putWaveData(w); } /// SAMPLE for (int i=0; itell()); sample->putSampleData(w); } /// PATTERN 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; } SafeWriter* DivEngine::saveDMF(unsigned char version) { // fail if version is not supported if (version>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 if (sys!=DIV_SYSTEM_GB) { int realVolMacroLen=i->std.volMacro.len; if (realVolMacroLen>127) realVolMacroLen=127; w->writeC(realVolMacroLen); if ((sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) && i->c64.volIsCutoff) { for (int j=0; jwriteI(i->std.volMacro.val[j]+18); } } else { for (int j=0; jwriteI(i->std.volMacro.val[j]); } } if (realVolMacroLen>0) { 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]); } 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(i->c64.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; }