From 3ab278d2365166fa620cf1d3702f68ae3ba23549 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 5 Feb 2024 14:08:53 -0500 Subject: [PATCH] split fileOps.cpp --- CMakeLists.txt | 9 +- src/engine/fileOps.cpp | 6702 -------------------------- src/engine/fileOps/dmf.cpp | 1906 ++++++++ src/engine/fileOps/fc.cpp | 708 +++ src/engine/fileOps/fileOpsCommon.cpp | 149 + src/engine/fileOps/fileOpsCommon.h | 54 + src/engine/fileOps/ftm.cpp | 611 +++ src/engine/fileOps/fur.cpp | 2710 +++++++++++ src/engine/fileOps/mod.cpp | 447 ++ src/engine/fileOps/s3m.cpp | 261 + 10 files changed, 6854 insertions(+), 6703 deletions(-) delete mode 100644 src/engine/fileOps.cpp create mode 100644 src/engine/fileOps/dmf.cpp create mode 100644 src/engine/fileOps/fc.cpp create mode 100644 src/engine/fileOps/fileOpsCommon.cpp create mode 100644 src/engine/fileOps/fileOpsCommon.h create mode 100644 src/engine/fileOps/ftm.cpp create mode 100644 src/engine/fileOps/fur.cpp create mode 100644 src/engine/fileOps/mod.cpp create mode 100644 src/engine/fileOps/s3m.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 51a543d11..33dba8239 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -613,6 +613,14 @@ src/engine/platform/oplAInterface.cpp src/engine/platform/ym2608Interface.cpp src/engine/platform/ym2610Interface.cpp +src/engine/fileOps/fileOpsCommon.cpp +src/engine/fileOps/dmf.cpp +src/engine/fileOps/fc.cpp +src/engine/fileOps/ftm.cpp +src/engine/fileOps/fur.cpp +src/engine/fileOps/mod.cpp +src/engine/fileOps/s3m.cpp + src/engine/blip_buf.c src/engine/brrUtils.c src/engine/safeReader.cpp @@ -625,7 +633,6 @@ src/engine/configEngine.cpp src/engine/dispatchContainer.cpp src/engine/engine.cpp src/engine/export.cpp -src/engine/fileOps.cpp src/engine/fileOpsIns.cpp src/engine/fileOpsSample.cpp src/engine/filter.cpp diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp deleted file mode 100644 index d4c02347a..000000000 --- a/src/engine/fileOps.cpp +++ /dev/null @@ -1,6702 +0,0 @@ -/** - * Furnace Tracker - multi-system chiptune tracker - * Copyright (C) 2021-2024 tildearrow and contributors - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "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 - if (!getConfInt("noDMFCompat",0)) { - ds.limitSlides=true; - ds.linearPitch=1; - ds.loopModality=0; - ds.properNoiseLayout=false; - ds.waveDutyIsVol=false; - // TODO: WHAT?! geodude.dmf fails when this is true - // but isn't that how Defle behaves??? - ds.resetMacroOnPorta=false; - ds.legacyVolumeSlides=true; - ds.compatibleArpeggio=true; - ds.noteOffResetsSlides=true; - ds.targetResetsSlides=true; - ds.arpNonPorta=false; - ds.algMacroBehavior=false; - ds.brokenShortcutSlides=false; - ds.ignoreDuplicateSlides=true; - ds.brokenDACMode=true; - ds.oneTickCut=false; - ds.newInsTriggersInPorta=true; - ds.arp0Reset=true; - ds.brokenSpeedSel=true; - ds.noSlidesOnFirstTick=false; - ds.rowResetsArpPos=false; - ds.ignoreJumpAtEnd=true; - ds.buggyPortaAfterSlide=true; - ds.gbInsAffectsEnvelope=true; - ds.ignoreDACModeOutsideIntendedChannel=false; - ds.e1e2AlsoTakePriority=true; - ds.fbPortaPause=true; - ds.snDutyReset=true; - ds.oldOctaveBoundary=false; - ds.noOPN2Vol=true; - ds.newVolumeScaling=false; - ds.volMacroLinger=false; - ds.brokenOutVol=true; - ds.brokenOutVol2=true; - ds.e1e2StopOnSameNote=true; - ds.brokenPortaArp=false; - ds.snNoLowPeriods=true; - ds.disableSampleMacro=true; - ds.preNoteNoEffect=true; - ds.oldDPCM=true; - ds.delayBehavior=0; - ds.jumpTreatment=2; - ds.oldAlwaysSetVolume=true; - - // 1.1 compat flags - if (ds.version>24) { - ds.waveDutyIsVol=true; - ds.legacyVolumeSlides=false; - } - - // Neo Geo detune is caused by Defle running Neo Geo at the wrong clock. - /* - if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT - || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT - || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { - ds.tuning=443.23; - } - */ - - // Genesis detuned on Defle v10 and earlier - /*if (ds.version<19 && ds.system[0]==DIV_SYSTEM_GENESIS) { - ds.tuning=443.23; - }*/ - // C64 detuned on Defle v11 and earlier - /*if (ds.version<21 && (ds.system[0]==DIV_SYSTEM_C64_6581 || ds.system[0]==DIV_SYSTEM_C64_8580)) { - ds.tuning=433.2; - }*/ - - // Game Boy arp+soundLen screwery - if (ds.system[0]==DIV_SYSTEM_GB) { - ds.systemFlags[0].set("enoughAlready",true); - } - } - - logI("reading module data..."); - if (ds.version>0x0c) { - ds.subsong[0]->hilightA=reader.readC(); - ds.subsong[0]->hilightB=reader.readC(); - } - - bool customTempo=false; - - ds.subsong[0]->timeBase=reader.readC(); - ds.subsong[0]->speeds.len=2; - ds.subsong[0]->speeds.val[0]=reader.readC(); - if (ds.version>0x07) { - ds.subsong[0]->speeds.val[1]=reader.readC(); - bool pal=reader.readC(); - ds.subsong[0]->hz=pal?60:50; - customTempo=reader.readC(); - } else { - ds.subsong[0]->speeds.len=1; - } - if (ds.version>0x0a) { - String hz=reader.readString(3); - if (customTempo) { - try { - ds.subsong[0]->hz=std::stoi(hz); - } catch (std::exception& e) { - logW("invalid custom Hz!"); - ds.subsong[0]->hz=60; - } - } - } - if (ds.version>0x17) { - ds.subsong[0]->patLen=reader.readI(); - } else { - ds.subsong[0]->patLen=(unsigned char)reader.readC(); - } - ds.subsong[0]->ordersLen=(unsigned char)reader.readC(); - - if (ds.subsong[0]->patLen<0) { - logE("pattern length is negative!"); - lastError="pattern lengrh is negative!"; - delete[] file; - return false; - } - if (ds.subsong[0]->patLen>256) { - logE("pattern length is too large!"); - lastError="pattern length is too large!"; - delete[] file; - return false; - } - if (ds.subsong[0]->ordersLen<0) { - logE("song length is negative!"); - lastError="song length is negative!"; - delete[] file; - return false; - } - if (ds.subsong[0]->ordersLen>127) { - logE("song is too long!"); - lastError="song is too long!"; - delete[] file; - return false; - } - - if (ds.version<20 && ds.version>3) { - ds.subsong[0]->arpLen=reader.readC(); - } else { - ds.subsong[0]->arpLen=1; - } - - if (ds.system[0]==DIV_SYSTEM_YMU759) { - switch (ds.subsong[0]->timeBase) { - case 0: - ds.subsong[0]->hz=248; - break; - case 1: - ds.subsong[0]->hz=200; - break; - case 2: - ds.subsong[0]->hz=100; - break; - case 3: - ds.subsong[0]->hz=50; - break; - case 4: - ds.subsong[0]->hz=25; - break; - case 5: - ds.subsong[0]->hz=20; - break; - default: - ds.subsong[0]->hz=248; - break; - } - ds.subsong[0]->timeBase=0; - addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system."); - } - - logV("%x",reader.tell()); - - logI("reading pattern matrix (%d * %d = %d)...",ds.subsong[0]->ordersLen,getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen*getChannelCount(ds.system[0])); - for (int i=0; iordersLen; j++) { - ds.subsong[0]->orders.ord[i][j]=reader.readC(); - if (ds.subsong[0]->orders.ord[i][j]>0x7f) { - logE("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]); - lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]); - delete[] file; - return false; - } - if (ds.version>0x18) { // 1.1 pattern names - ds.subsong[0]->pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC()); - } - } - if (ds.version>0x03 && ds.version<0x06 && i<16) { - historicColIns[i]=reader.readC(); - } - } - - logV("%x",reader.tell()); - - if (ds.version>0x05) { - ds.insLen=(unsigned char)reader.readC(); - } else { - ds.insLen=16; - } - logI("reading instruments (%d)...",ds.insLen); - if (ds.insLen>0) ds.ins.reserve(ds.insLen); - for (int i=0; i0x05) { - ins->name=reader.readString((unsigned char)reader.readC()); - } - logD("%d name: %s",i,ins->name.c_str()); - if (ds.version<0x0b) { - // instruments in ancient versions were all FM. - mode=1; - } else { - mode=reader.readC(); - if (mode>1) logW("%d: invalid instrument mode %d!",i,mode); - } - ins->type=mode?DIV_INS_FM:DIV_INS_STD; - if (ds.system[0]==DIV_SYSTEM_GB) { - ins->type=DIV_INS_GB; - } - if (ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) { - ins->type=DIV_INS_C64; - } - if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT - || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT - || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { - if (!mode) { - ins->type=DIV_INS_AY; - } - } - if (ds.system[0]==DIV_SYSTEM_PCE) { - ins->type=DIV_INS_PCE; - } - if ((ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) && ins->type==DIV_INS_FM) { - ins->type=DIV_INS_OPLL; - } - if (ds.system[0]==DIV_SYSTEM_YMU759) { - ins->type=DIV_INS_OPL; - } - if (ds.system[0]==DIV_SYSTEM_ARCADE) { - ins->type=DIV_INS_OPM; - } - if ((ds.system[0]==DIV_SYSTEM_NES || ds.system[0]==DIV_SYSTEM_NES_VRC7 || ds.system[0]==DIV_SYSTEM_NES_FDS) && ins->type==DIV_INS_STD) { - ins->type=DIV_INS_NES; - } - - if (mode) { // FM - if (ds.version>0x05) { - ins->fm.alg=reader.readC(); - if (ds.version<0x13) { - reader.readC(); - } - ins->fm.fb=reader.readC(); - if (ds.version<0x13) { - reader.readC(); - } - ins->fm.fms=reader.readC(); - if (ds.version<0x13) { - reader.readC(); - ins->fm.ops=2+reader.readC()*2; - if (ds.system[0]!=DIV_SYSTEM_YMU759) ins->fm.ops=4; - } else { - ins->fm.ops=4; - } - ins->fm.ams=reader.readC(); - } else { - ins->fm.alg=reader.readC(); - reader.readC(); - ins->fm.fb=reader.readC(); - reader.readC(); // apparently an index of sorts starting from 0x59? - ins->fm.fms=reader.readC(); - reader.readC(); // 0x59+index? - ins->fm.ops=2+reader.readC()*2; - } - - logD("ALG %d FB %d FMS %d AMS %d OPS %d",ins->fm.alg,ins->fm.fb,ins->fm.fms,ins->fm.ams,ins->fm.ops); - if (ins->fm.ops!=2 && ins->fm.ops!=4) { - logE("invalid op count %d. did we read it wrong?",ins->fm.ops); - lastError="file is corrupt or unreadable at operators"; - delete[] file; - return false; - } - - for (int j=0; jfm.ops; j++) { - ins->fm.op[j].am=reader.readC(); - ins->fm.op[j].ar=reader.readC(); - if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { - ins->fm.op[j].ar&=15; - } - if (ds.version<0x13) { - ins->fm.op[j].dam=reader.readC(); - } - ins->fm.op[j].dr=reader.readC(); - if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { - ins->fm.op[j].dr&=15; - } - if (ds.version<0x13) { - ins->fm.op[j].dvb=reader.readC(); - ins->fm.op[j].egt=reader.readC(); - ins->fm.op[j].ksl=reader.readC(); - if (ds.version<0x11) { // don't know when did this change - ins->fm.op[j].ksr=reader.readC(); - } - } - ins->fm.op[j].mult=reader.readC(); - ins->fm.op[j].rr=reader.readC(); - ins->fm.op[j].sl=reader.readC(); - if (ds.version<0x13) { - ins->fm.op[j].sus=reader.readC(); - } - ins->fm.op[j].tl=reader.readC(); - if (ds.version<0x13) { - ins->fm.op[j].vib=reader.readC(); - ins->fm.op[j].ws=reader.readC(); - } else { - if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { - if (j==0) { - ins->fm.opllPreset=reader.readC(); - } else { - reader.readC(); - } - } else { - ins->fm.op[j].dt2=reader.readC(); - } - } - if (ds.version>0x05) { - if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { - ins->fm.op[j].ksr=reader.readC(); - ins->fm.op[j].vib=reader.readC(); - ins->fm.op[j].ksl=reader.readC(); - ins->fm.op[j].ssgEnv=reader.readC(); - } else { - ins->fm.op[j].rs=reader.readC(); - ins->fm.op[j].dt=reader.readC(); - ins->fm.op[j].d2r=reader.readC(); - ins->fm.op[j].ssgEnv=reader.readC(); - } - } - if (ds.version<0x12) { // before version 10 all ops were responsive to volume - ins->fm.op[j].kvs=1; - } - - logD("OP%d: AM %d AR %d DAM %d DR %d DVB %d EGT %d KSL %d MULT %d RR %d SL %d SUS %d TL %d VIB %d WS %d RS %d DT %d D2R %d SSG-EG %d",j, - ins->fm.op[j].am, - ins->fm.op[j].ar, - ins->fm.op[j].dam, - ins->fm.op[j].dr, - ins->fm.op[j].dvb, - ins->fm.op[j].egt, - ins->fm.op[j].ksl, - ins->fm.op[j].mult, - ins->fm.op[j].rr, - ins->fm.op[j].sl, - ins->fm.op[j].sus, - ins->fm.op[j].tl, - ins->fm.op[j].vib, - ins->fm.op[j].ws, - ins->fm.op[j].rs, - ins->fm.op[j].dt, - ins->fm.op[j].d2r, - ins->fm.op[j].ssgEnv - ); - } - - // swap alg operator 2 and 3 if YMU759 - if (ds.system[0]==DIV_SYSTEM_YMU759 && ins->fm.ops==4) { - DivInstrumentFM::Operator oldOp=ins->fm.op[2]; - ins->fm.op[2]=ins->fm.op[1]; - ins->fm.op[1]=oldOp; - - if (ins->fm.alg==1) { - ins->fm.alg=2; - } else if (ins->fm.alg==2) { - ins->fm.alg=1; - } - } - } else { // STD - if (ds.system[0]!=DIV_SYSTEM_GB || ds.version<0x12) { - ins->std.volMacro.len=reader.readC(); - for (int j=0; jstd.volMacro.len; j++) { - if (ds.version<0x0e) { - ins->std.volMacro.val[j]=reader.readC(); - } else { - ins->std.volMacro.val[j]=reader.readI(); - } - } - if (ins->std.volMacro.len>0) { - ins->std.volMacro.open=true; - ins->std.volMacro.loop=reader.readC(); - } else { - ins->std.volMacro.open=false; - } - } - - ins->std.arpMacro.len=reader.readC(); - for (int j=0; jstd.arpMacro.len; j++) { - if (ds.version<0x0e) { - ins->std.arpMacro.val[j]=reader.readC(); - } else { - ins->std.arpMacro.val[j]=reader.readI(); - } - } - if (ins->std.arpMacro.len>0) { - ins->std.arpMacro.loop=reader.readC(); - ins->std.arpMacro.open=true; - } else { - ins->std.arpMacro.open=false; - } - if (ds.version>0x0f) { - ins->std.arpMacro.mode=reader.readC(); - } - if (!ins->std.arpMacro.mode) { - for (int j=0; jstd.arpMacro.len; j++) { - ins->std.arpMacro.val[j]-=12; - } - } else { - ins->std.arpMacro.mode=0; - for (int j=0; jstd.arpMacro.len; j++) { - ins->std.arpMacro.val[j]^=0x40000000; - } - if (ins->std.arpMacro.loop==255 && ins->std.arpMacro.len<255) { - ins->std.arpMacro.val[ins->std.arpMacro.len++]=0; - } - } - - ins->std.dutyMacro.len=reader.readC(); - for (int j=0; jstd.dutyMacro.len; j++) { - if (ds.version<0x0e) { - ins->std.dutyMacro.val[j]=reader.readC(); - } else { - ins->std.dutyMacro.val[j]=reader.readI(); - } - /*if ((ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) && ins->std.dutyMacro.val[j]>24) { - ins->std.dutyMacro.val[j]=24; - }*/ - } - if (ins->std.dutyMacro.len>0) { - ins->std.dutyMacro.open=true; - ins->std.dutyMacro.loop=reader.readC(); - } else { - ins->std.dutyMacro.open=false; - } - - ins->std.waveMacro.len=reader.readC(); - for (int j=0; jstd.waveMacro.len; j++) { - if (ds.version<0x0e) { - ins->std.waveMacro.val[j]=reader.readC(); - } else { - ins->std.waveMacro.val[j]=reader.readI(); - } - } - if (ins->std.waveMacro.len>0) { - ins->std.waveMacro.open=true; - ins->std.waveMacro.loop=reader.readC(); - } else { - ins->std.waveMacro.open=false; - } - - if (ds.system[0]==DIV_SYSTEM_C64_6581 || ds.system[0]==DIV_SYSTEM_C64_8580) { - bool volIsCutoff=false; - - ins->c64.triOn=reader.readC(); - ins->c64.sawOn=reader.readC(); - ins->c64.pulseOn=reader.readC(); - ins->c64.noiseOn=reader.readC(); - - ins->c64.a=reader.readC(); - ins->c64.d=reader.readC(); - ins->c64.s=reader.readC(); - ins->c64.r=reader.readC(); - - ins->c64.duty=(reader.readC()*4095)/100; - - ins->c64.ringMod=reader.readC(); - ins->c64.oscSync=reader.readC(); - ins->c64.toFilter=reader.readC(); - if (ds.version<0x11) { - volIsCutoff=reader.readI(); - } else { - volIsCutoff=reader.readC(); - } - ins->c64.initFilter=reader.readC(); - - ins->c64.res=reader.readC(); - ins->c64.cut=(reader.readC()*2047)/100; - ins->c64.hp=reader.readC(); - ins->c64.bp=reader.readC(); - ins->c64.lp=reader.readC(); - ins->c64.ch3off=reader.readC(); - - // weird storage - if (volIsCutoff) { - // move to alg (new cutoff) - ins->std.algMacro.len=ins->std.volMacro.len; - ins->std.algMacro.loop=ins->std.volMacro.loop; - ins->std.algMacro.rel=ins->std.volMacro.rel; - for (int j=0; jstd.algMacro.len; j++) { - ins->std.algMacro.val[j]=-(ins->std.volMacro.val[j]-18); - } - ins->std.volMacro.len=0; - memset(ins->std.volMacro.val,0,256*sizeof(int)); - } - for (int j=0; jstd.dutyMacro.len; j++) { - ins->std.dutyMacro.val[j]-=12; - } - } - - if (ds.system[0]==DIV_SYSTEM_GB && ds.version>0x11) { - ins->gb.envVol=reader.readC(); - ins->gb.envDir=reader.readC(); - ins->gb.envLen=reader.readC(); - ins->gb.soundLen=reader.readC(); - ins->std.volMacro.open=false; - - logD("GB data: vol %d dir %d len %d sl %d",ins->gb.envVol,ins->gb.envDir,ins->gb.envLen,ins->gb.soundLen); - } else if (ds.system[0]==DIV_SYSTEM_GB) { - // set software envelope flag - ins->gb.softEnv=true; - // try to convert macro to envelope in case the user decides to switch to them - if (ins->std.volMacro.len>0) { - ins->gb.envVol=ins->std.volMacro.val[0]; - if (ins->std.volMacro.val[0]std.volMacro.val[1]) { - ins->gb.envDir=true; - } - if (ins->std.volMacro.val[ins->std.volMacro.len-1]==0) { - ins->gb.soundLen=ins->std.volMacro.len*2; - } - } - } - } - - ds.ins.push_back(ins); - } - - if (ds.version>0x0b) { - ds.waveLen=(unsigned char)reader.readC(); - logI("reading wavetables (%d)...",ds.waveLen); - if (ds.waveLen>0) ds.wave.reserve(ds.waveLen); - for (int i=0; ilen=(unsigned char)reader.readI(); - if (ds.system[0]==DIV_SYSTEM_GB) { - wave->max=15; - } - if (ds.system[0]==DIV_SYSTEM_NES_FDS) { - wave->max=63; - } - if (wave->len>65) { - logE("invalid wave length %d. are we doing something wrong?",wave->len); - lastError="file is corrupt or unreadable at wavetables"; - delete[] file; - return false; - } - logD("%d length %d",i,wave->len); - for (int j=0; jlen; j++) { - if (ds.version<0x0e) { - wave->data[j]=reader.readC(); - } else { - wave->data[j]=reader.readI(); - } - wave->data[j]&=wave->max; - } - // #FDS4Bit - if (ds.system[0]==DIV_SYSTEM_NES_FDS && ds.version<0x1a) { - for (int j=0; jlen; j++) { - wave->data[j]*=4; - } - } - ds.wave.push_back(wave); - } - - // sometimes there's a single length 0 wavetable in the file. I don't know why. - if (ds.waveLen==1) { - if (ds.wave[0]->len==0) { - ds.clearWavetables(); - } - } - } - - logV("%x",reader.tell()); - - logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen); - for (int i=0; ipat[i]; - if (ds.version<0x0a) { - chan.effectCols=1; - } else { - chan.effectCols=reader.readC(); - } - logD("%d fx rows: %d",i,chan.effectCols); - if (chan.effectCols>4 || chan.effectCols<1) { - logE("invalid effect column count %d. are you sure everything is ok?",chan.effectCols); - lastError="file is corrupt or unreadable at effect columns"; - delete[] file; - return false; - } - for (int j=0; jordersLen; j++) { - DivPattern* pat=chan.getPattern(ds.subsong[0]->orders.ord[i][j],true); - if (ds.version>0x08) { // current pattern format - for (int k=0; kpatLen; k++) { - // note - pat->data[k][0]=reader.readS(); - // octave - pat->data[k][1]=reader.readS(); - if (ds.system[0]==DIV_SYSTEM_SMS && ds.version<0x0e && pat->data[k][1]>0) { - // apparently it was up one octave before - pat->data[k][1]--; - } else if (ds.system[0]==DIV_SYSTEM_GENESIS && ds.version<0x0e && pat->data[k][1]>0 && i>5) { - // ditto - pat->data[k][1]--; - } - if (ds.version<0x12) { - if (ds.system[0]==DIV_SYSTEM_GB && i==3 && pat->data[k][1]>0) { - // back then noise was 2 octaves lower - pat->data[k][1]-=2; - } - } - if (ds.system[0]==DIV_SYSTEM_YMU759 && pat->data[k][0]!=0) { - // apparently YMU759 is stored 2 octaves lower - pat->data[k][1]+=2; - } - if (pat->data[k][0]==0 && pat->data[k][1]!=0) { - logD("what? %d:%d:%d note %d octave %d",i,j,k,pat->data[k][0],pat->data[k][1]); - pat->data[k][0]=12; - pat->data[k][1]--; - } - // volume - pat->data[k][3]=reader.readS(); - if (ds.version<0x0a) { - // back then volume was stored as 00-ff instead of 00-7f/0-f - if (i>5) { - pat->data[k][3]>>=4; - } else { - pat->data[k][3]>>=1; - } - } - if (ds.version<0x12) { - if (ds.system[0]==DIV_SYSTEM_GB && i==2 && pat->data[k][3]>0) { - // volume range of GB wave channel was 0-3 rather than 0-F - pat->data[k][3]=(pat->data[k][3]&3)*5; - } - } - for (int l=0; ldata[k][4+(l<<1)]=reader.readS(); - pat->data[k][5+(l<<1)]=reader.readS(); - - if (ds.version<0x14) { - if (pat->data[k][4+(l<<1)]==0xe5 && pat->data[k][5+(l<<1)]!=-1) { - pat->data[k][5+(l<<1)]=128+((pat->data[k][5+(l<<1)]-128)/4); - } - } - } - // instrument - pat->data[k][2]=reader.readS(); - - // this is sad - if (ds.system[0]==DIV_SYSTEM_NES_FDS) { - if (i==5 && pat->data[k][2]!=-1) { - if (pat->data[k][2]>=0 && pat->data[k][2]data[k][2]]->type=DIV_INS_FDS; - } - } - } - } - } else { // historic pattern format - if (i<16) pat->data[0][2]=historicColIns[i]; - for (int k=0; kpatLen; k++) { - // note - pat->data[k][0]=reader.readC(); - // octave - pat->data[k][1]=reader.readC(); - if (pat->data[k][0]!=0) { - // YMU759 is stored 2 octaves lower - pat->data[k][1]+=2; - } - if (pat->data[k][0]==0 && pat->data[k][1]!=0) { - logD("what? %d:%d:%d note %d octave %d",i,j,k,pat->data[k][0],pat->data[k][1]); - pat->data[k][0]=12; - pat->data[k][1]--; - } - // volume and effect - unsigned char vol=reader.readC(); - unsigned char fx=reader.readC(); - unsigned char fxVal=reader.readC(); - pat->data[k][3]=(vol==0x80 || vol==0xff)?-1:vol; - // effect - pat->data[k][4]=(fx==0x80 || fx==0xff)?-1:fx; - pat->data[k][5]=(fxVal==0x80 || fx==0xff)?-1:fxVal; - // instrument - if (ds.version>0x05) { - pat->data[k][2]=reader.readC(); - if (pat->data[k][2]==0x80 || pat->data[k][2]==0xff) pat->data[k][2]=-1; - } - } - } - } - } - - int ymuSampleRate=20; - - ds.sampleLen=(unsigned char)reader.readC(); - logI("reading samples (%d)...",ds.sampleLen); - if (ds.version<0x0b && ds.sampleLen>0) { - // it appears this byte stored the YMU759 sample rate - ymuSampleRate=reader.readC(); - } - if (ds.sampleLen>0) ds.sample.reserve(ds.sampleLen); - for (int i=0; i0x16) { - sample->name=reader.readString((unsigned char)reader.readC()); - } else { - sample->name=""; - } - logD("%d name %s (%d)",i,sample->name.c_str(),length); - sample->rate=22050; - if (ds.version>=0x0b) { - sample->rate=fileToDivRate(reader.readC()); - sample->centerRate=sample->rate; - pitch=reader.readC(); - vol=reader.readC(); - } - if (ds.version<=0x08) { - sample->rate=ymuSampleRate*400; - } - if (ds.version>0x15) { - sample->depth=(DivSampleDepth)reader.readC(); - if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) { - logW("%d: sample depth is wrong! (%d)",i,(int)sample->depth); - sample->depth=DIV_SAMPLE_DEPTH_16BIT; - } - } else { - if (ds.version>0x08) { - sample->depth=DIV_SAMPLE_DEPTH_16BIT; - } else { - // it appears samples were stored as ADPCM back then - sample->depth=DIV_SAMPLE_DEPTH_YMZ_ADPCM; - } - } - if (ds.version>=0x1b) { - // what the hell man... - cutStart=reader.readI(); - cutEnd=reader.readI(); - logV("cutStart: %d cutEnd: %d",cutStart,cutEnd); - } - if (length>0) { - if (ds.version>0x08) { - if (ds.version<0x0b) { - data=new short[1+(length/2)]; - reader.read(data,length); - length/=2; - } else { - data=new short[length]; - reader.read(data,length*2); - } - -#ifdef TA_BIG_ENDIAN - // convert to big-endian - for (int pos=0; pos>8)); - } -#endif - - int scaledLen=ceil((double)length/samplePitches[pitch]); - - if (scaledLen>0) { - // resample - logD("%d: scaling from %d...",i,pitch); - - short* newData=new short[scaledLen]; - memset(newData,0,scaledLen*sizeof(short)); - int k=0; - float mult=(float)(vol)/50.0f; - for (double j=0; j=scaledLen) { - break; - } - if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { - float next=(float)(data[(unsigned int)j]-0x80)*mult; - newData[k++]=fmin(fmax(next,-128),127); - } else { - float next=(float)data[(unsigned int)j]*mult; - newData[k++]=fmin(fmax(next,-32768),32767); - } - } - - delete[] data; - data=newData; - } - - logV("length: %d. scaledLen: %d.",length,scaledLen); - - if (ds.version>=0x1b) { - if (cutStart<0 || cutStart>scaledLen) { - logE("cutStart is out of range! (%d, scaledLen: %d)",cutStart,scaledLen); - lastError="file is corrupt or unreadable at samples"; - delete[] file; - return false; - } - if (cutEnd<0 || cutEnd>scaledLen) { - logE("cutEnd is out of range! (%d, scaledLen: %d)",cutEnd,scaledLen); - lastError="file is corrupt or unreadable at samples"; - delete[] file; - return false; - } - if (cutEndinit(scaledLen)) { - logE("%d: error while initializing sample!",i); - } else { - for (int i=0; idepth==DIV_SAMPLE_DEPTH_8BIT) { - sample->data8[i]=data[i]; - } else { - sample->data16[i]=data[i]; - } - } - } - - delete[] data; - } else { - // YMZ ADPCM - adpcmData=new unsigned char[length]; - logV("%x",reader.tell()); - reader.read(adpcmData,length); - for (int i=0; i>4); - } - if (!sample->init(length*2)) { - logE("%d: error while initializing sample!",i); - } - - memcpy(sample->dataZ,adpcmData,length); - delete[] adpcmData; - } - } - ds.sample.push_back(sample); - } - - if (reader.tell()>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; - } - if (ds.version<168) { - ds.preNoteNoEffect=true; - } - if (ds.version<183) { - ds.oldDPCM=true; - } - if (ds.version<184) { - ds.resetArpPhaseOnNewNote=false; - } - if (ds.version<188) { - ds.ceilVolumeScaling=false; - } - if (ds.version<191) { - ds.oldAlwaysSetVolume=true; - } - ds.isDMF=false; - - reader.readS(); // reserved - int infoSeek=reader.readI(); - - if (!reader.seek(infoSeek,SEEK_SET)) { - logE("couldn't seek to info header at %d!",infoSeek); - lastError="couldn't seek to info header!"; - delete[] file; - return false; - } - - // read header - reader.read(magic,4); - if (strcmp(magic,"INFO")!=0) { - logE("invalid info header!"); - lastError="invalid info header!"; - delete[] file; - return false; - } - reader.readI(); - - subSong->timeBase=reader.readC(); - subSong->speeds.len=2; - subSong->speeds.val[0]=reader.readC(); - subSong->speeds.val[1]=reader.readC(); - subSong->arpLen=reader.readC(); - subSong->hz=reader.readF(); - - subSong->patLen=reader.readS(); - subSong->ordersLen=reader.readS(); - - subSong->hilightA=reader.readC(); - subSong->hilightB=reader.readC(); - - ds.insLen=reader.readS(); - ds.waveLen=reader.readS(); - ds.sampleLen=reader.readS(); - int numberOfPats=reader.readI(); - - if (subSong->patLen<0) { - logE("pattern length is negative!"); - lastError="pattern lengrh is negative!"; - delete[] file; - return false; - } - if (subSong->patLen>DIV_MAX_ROWS) { - logE("pattern length is too large!"); - lastError="pattern length is too large!"; - delete[] file; - return false; - } - if (subSong->ordersLen<0) { - logE("song length is negative!"); - lastError="song length is negative!"; - delete[] file; - return false; - } - if (subSong->ordersLen>DIV_MAX_PATTERNS) { - logE("song is too long!"); - lastError="song is too long!"; - delete[] file; - return false; - } - if (ds.insLen<0 || ds.insLen>256) { - logE("invalid instrument count!"); - lastError="invalid instrument count!"; - delete[] file; - return false; - } - if (ds.waveLen<0 || ds.waveLen>256) { - logE("invalid wavetable count!"); - lastError="invalid wavetable count!"; - delete[] file; - return false; - } - if (ds.sampleLen<0 || ds.sampleLen>256) { - logE("invalid sample count!"); - lastError="invalid sample count!"; - delete[] file; - return false; - } - if (numberOfPats<0) { - logE("invalid pattern count!"); - lastError="invalid pattern count!"; - delete[] file; - return false; - } - - logD("systems:"); - ds.systemLen=0; - for (int i=0; iDIV_MAX_CHANS) { - tchans=DIV_MAX_CHANS; - logW("too many channels!"); - } - logV("system len: %d",ds.systemLen); - if (ds.systemLen<1) { - logE("zero chips!"); - lastError="zero chips!"; - delete[] file; - return false; - } - - // system volume - for (int i=0; ii; j--) { - ds.system[j]=ds.system[j-1]; - ds.systemVol[j]=ds.systemVol[j-1]; - ds.systemPan[j]=ds.systemPan[j-1]; - } - if (++ds.systemLen>DIV_MAX_CHIPS) ds.systemLen=DIV_MAX_CHIPS; - - if (ds.system[i]==DIV_SYSTEM_GENESIS) { - ds.system[i]=DIV_SYSTEM_YM2612; - if (i<31) { - ds.system[i+1]=DIV_SYSTEM_SMS; - ds.systemVol[i+1]=ds.systemVol[i]*0.375f; - } - } - if (ds.system[i]==DIV_SYSTEM_GENESIS_EXT) { - ds.system[i]=DIV_SYSTEM_YM2612_EXT; - if (i<31) { - ds.system[i+1]=DIV_SYSTEM_SMS; - ds.systemVol[i+1]=ds.systemVol[i]*0.375f; - } - } - if (ds.system[i]==DIV_SYSTEM_ARCADE) { - ds.system[i]=DIV_SYSTEM_YM2151; - if (i<31) { - ds.system[i+1]=DIV_SYSTEM_SEGAPCM_COMPAT; - } - } - i++; - } - } - - ds.name=reader.readString(); - ds.author=reader.readString(); - logI("%s by %s",ds.name.c_str(),ds.author.c_str()); - - if (ds.version>=33) { - ds.tuning=reader.readF(); - } else { - reader.readI(); - } - - // compatibility flags - if (ds.version>=37) { - ds.limitSlides=reader.readC(); - ds.linearPitch=reader.readC(); - ds.loopModality=reader.readC(); - if (ds.version>=43) { - ds.properNoiseLayout=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=43) { - ds.waveDutyIsVol=reader.readC(); - } else { - reader.readC(); - } - - if (ds.version>=45) { - ds.resetMacroOnPorta=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=45) { - ds.legacyVolumeSlides=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=45) { - ds.compatibleArpeggio=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=45) { - ds.noteOffResetsSlides=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=45) { - ds.targetResetsSlides=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=47) { - ds.arpNonPorta=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=47) { - ds.algMacroBehavior=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=49) { - ds.brokenShortcutSlides=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=50) { - ds.ignoreDuplicateSlides=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=62) { - ds.stopPortaOnNoteOff=reader.readC(); - ds.continuousVibrato=reader.readC(); - } else { - reader.readC(); - reader.readC(); - } - if (ds.version>=64) { - ds.brokenDACMode=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=65) { - ds.oneTickCut=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=66) { - ds.newInsTriggersInPorta=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=69) { - ds.arp0Reset=reader.readC(); - } else { - reader.readC(); - } - } else { - for (int i=0; i<20; i++) reader.readC(); - } - - // pointers - for (int i=0; iordersLen); - for (int i=0; iordersLen; j++) { - subSong->orders.ord[i][j]=reader.readC(); - } - } - - for (int i=0; ipat[i].effectCols=reader.readC(); - if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>DIV_MAX_EFFECTS) { - logE("channel %d has zero or too many effect columns! (%d)",i,subSong->pat[i].effectCols); - lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,subSong->pat[i].effectCols); - delete[] file; - return false; - } - } - - if (ds.version>=39) { - for (int i=0; ichanShow[i]=reader.readC(); - subSong->chanShowChanOsc[i]=subSong->chanShow[i]; - } else { - unsigned char tempchar=reader.readC(); - subSong->chanShow[i]=tempchar&1; - subSong->chanShowChanOsc[i]=(tempchar&2); - } - } - - for (int i=0; ichanCollapse[i]=reader.readC(); - } - - if (ds.version<92) { - for (int i=0; ichanCollapse[i]>0) subSong->chanCollapse[i]=3; - } - } - - for (int i=0; ichanName[i]=reader.readString(); - } - - for (int i=0; ichanShortName[i]=reader.readString(); - } - - ds.notes=reader.readString(); - } - - if (ds.version>=59) { - ds.masterVol=reader.readF(); - } else { - ds.masterVol=2.0f; - } - - if (ds.version>=70) { - // extended compat flags - ds.brokenSpeedSel=reader.readC(); - if (ds.version>=71) { - ds.noSlidesOnFirstTick=reader.readC(); - ds.rowResetsArpPos=reader.readC(); - ds.ignoreJumpAtEnd=reader.readC(); - } else { - reader.readC(); - reader.readC(); - reader.readC(); - } - if (ds.version>=72) { - ds.buggyPortaAfterSlide=reader.readC(); - ds.gbInsAffectsEnvelope=reader.readC(); - } else { - reader.readC(); - reader.readC(); - } - if (ds.version>=78) { - ds.sharedExtStat=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=83) { - ds.ignoreDACModeOutsideIntendedChannel=reader.readC(); - ds.e1e2AlsoTakePriority=reader.readC(); - } else { - reader.readC(); - reader.readC(); - } - if (ds.version>=84) { - ds.newSegaPCM=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=85) { - ds.fbPortaPause=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=86) { - ds.snDutyReset=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=90) { - ds.pitchMacroIsLinear=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=94) { - ds.pitchSlideSpeed=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=97) { - ds.oldOctaveBoundary=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=98) { - ds.noOPN2Vol=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=99) { - ds.newVolumeScaling=reader.readC(); - ds.volMacroLinger=reader.readC(); - ds.brokenOutVol=reader.readC(); - } else { - reader.readC(); - reader.readC(); - reader.readC(); - } - if (ds.version>=100) { - ds.e1e2StopOnSameNote=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=101) { - ds.brokenPortaArp=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=108) { - ds.snNoLowPeriods=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=110) { - ds.delayBehavior=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=113) { - ds.jumpTreatment=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=115) { - ds.autoSystem=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=117) { - ds.disableSampleMacro=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=121) { - ds.brokenOutVol2=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=130) { - ds.oldArpStrategy=reader.readC(); - } else { - reader.readC(); - } - } - - // first song virtual tempo - if (ds.version>=96) { - subSong->virtualTempoN=reader.readS(); - subSong->virtualTempoD=reader.readS(); - } else { - reader.readI(); - } - - // subsongs - if (ds.version>=95) { - subSong->name=reader.readString(); - subSong->notes=reader.readString(); - numberOfSubSongs=(unsigned char)reader.readC(); - reader.readC(); // reserved - reader.readC(); - reader.readC(); - // pointers - for (int i=0; i=103) { - ds.systemName=reader.readString(); - ds.category=reader.readString(); - ds.nameJ=reader.readString(); - ds.authorJ=reader.readString(); - ds.systemNameJ=reader.readString(); - ds.categoryJ=reader.readString(); - } else { - ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); - ds.autoSystem=true; - } - - // system output config - if (ds.version>=135) { - for (int i=0; i0) ds.patchbay.reserve(conns); - for (unsigned int i=0; i=136) ds.patchbayAuto=reader.readC(); - - if (ds.version>=138) { - ds.brokenPortaLegato=reader.readC(); - if (ds.version>=155) { - ds.brokenFMOff=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=168) { - ds.preNoteNoEffect=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=183) { - ds.oldDPCM=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=184) { - ds.resetArpPhaseOnNewNote=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=188) { - ds.ceilVolumeScaling=reader.readC(); - } else { - reader.readC(); - } - if (ds.version>=191) { - ds.oldAlwaysSetVolume=reader.readC(); - } else { - reader.readC(); - } - for (int i=0; i<1; i++) { - reader.readC(); - } - } - - if (ds.version>=139) { - subSong->speeds.len=reader.readC(); - for (int i=0; i<16; i++) { - subSong->speeds.val[i]=reader.readC(); - } - - // grooves - unsigned char grooveCount=reader.readC(); - ds.grooves.reserve(grooveCount); - for (int i=0; i=156) { - assetDirPtr[0]=reader.readI(); - assetDirPtr[1]=reader.readI(); - assetDirPtr[2]=reader.readI(); - } - - // read system flags - if (ds.version>=119) { - logD("reading chip flags..."); - for (int i=0; i=156) { - logD("reading asset directories..."); - - if (!reader.seek(assetDirPtr[0],SEEK_SET)) { - logE("couldn't seek to ins dir!"); - lastError=fmt::sprintf("couldn't read instrument directory"); - ds.unload(); - delete[] file; - return false; - } - if (readAssetDirData(reader,ds.insDir)!=DIV_DATA_SUCCESS) { - lastError="invalid instrument directory data!"; - ds.unload(); - delete[] file; - return false; - } - - if (!reader.seek(assetDirPtr[1],SEEK_SET)) { - logE("couldn't seek to wave dir!"); - lastError=fmt::sprintf("couldn't read wavetable directory"); - ds.unload(); - delete[] file; - return false; - } - if (readAssetDirData(reader,ds.waveDir)!=DIV_DATA_SUCCESS) { - lastError="invalid wavetable directory data!"; - ds.unload(); - delete[] file; - return false; - } - - if (!reader.seek(assetDirPtr[2],SEEK_SET)) { - logE("couldn't seek to sample dir!"); - lastError=fmt::sprintf("couldn't read sample directory"); - ds.unload(); - delete[] file; - return false; - } - if (readAssetDirData(reader,ds.sampleDir)!=DIV_DATA_SUCCESS) { - lastError="invalid sample directory data!"; - ds.unload(); - delete[] file; - return false; - } - } - - // read subsongs - if (ds.version>=95) { - ds.subsong.reserve(numberOfSubSongs); - for (int i=0; itimeBase=reader.readC(); - subSong->speeds.len=2; - subSong->speeds.val[0]=reader.readC(); - subSong->speeds.val[1]=reader.readC(); - subSong->arpLen=reader.readC(); - subSong->hz=reader.readF(); - - subSong->patLen=reader.readS(); - subSong->ordersLen=reader.readS(); - - subSong->hilightA=reader.readC(); - subSong->hilightB=reader.readC(); - - if (ds.version>=96) { - subSong->virtualTempoN=reader.readS(); - subSong->virtualTempoD=reader.readS(); - } else { - reader.readI(); - } - - subSong->name=reader.readString(); - subSong->notes=reader.readString(); - - logD("reading orders of subsong %d (%d)...",i+1,subSong->ordersLen); - for (int j=0; jordersLen; k++) { - subSong->orders.ord[j][k]=reader.readC(); - } - } - - for (int i=0; ipat[i].effectCols=reader.readC(); - } - - for (int i=0; ichanShow[i]=reader.readC(); - subSong->chanShowChanOsc[i]=subSong->chanShow[i]; - } else { - unsigned char tempchar=reader.readC(); - subSong->chanShow[i]=tempchar&1; - subSong->chanShowChanOsc[i]=tempchar&2; - } - } - - for (int i=0; ichanCollapse[i]=reader.readC(); - } - - for (int i=0; ichanName[i]=reader.readString(); - } - - for (int i=0; ichanShortName[i]=reader.readString(); - } - - if (ds.version>=139) { - subSong->speeds.len=reader.readC(); - for (int i=0; i<16; i++) { - subSong->speeds.val[i]=reader.readC(); - } - } - } - } - - // read instruments - ds.ins.reserve(ds.insLen); - for (int i=0; ireadInsData(reader,ds.version)!=DIV_DATA_SUCCESS) { - lastError="invalid instrument header/data!"; - ds.unload(); - delete ins; - delete[] file; - return false; - } - - ds.ins.push_back(ins); - } - - // read wavetables - ds.wave.reserve(ds.waveLen); - for (int i=0; ireadWaveData(reader,ds.version)!=DIV_DATA_SUCCESS) { - lastError="invalid wavetable header/data!"; - ds.unload(); - delete wave; - delete[] file; - return false; - } - - ds.wave.push_back(wave); - } - - // read samples - ds.sample.reserve(ds.sampleLen); - for (int i=0; ireadSampleData(reader,ds.version)!=DIV_DATA_SUCCESS) { - lastError="invalid sample header/data!"; - ds.unload(); - delete sample; - delete[] file; - return false; - } - - ds.sample.push_back(sample); - } - - // read patterns - for (unsigned int i: patPtr) { - bool isNewFormat=false; - if (!reader.seek(i,SEEK_SET)) { - logE("couldn't seek to pattern in %x!",i); - lastError=fmt::sprintf("couldn't seek to pattern in %x!",i); - ds.unload(); - delete[] file; - return false; - } - reader.read(magic,4); - logD("reading pattern in %x...",i); - if (strcmp(magic,"PATR")!=0) { - if (strcmp(magic,"PATN")!=0 || ds.version<157) { - logE("%x: invalid pattern header!",i); - lastError="invalid pattern header!"; - ds.unload(); - delete[] file; - return false; - } else { - isNewFormat=true; - } - } - reader.readI(); - - if (isNewFormat) { - int subs=(unsigned char)reader.readC(); - int chan=(unsigned char)reader.readC(); - int index=reader.readS(); - - logD("- %d, %d, %d (new)",subs,chan,index); - - if (chan<0 || chan>=tchans) { - logE("pattern channel out of range!",i); - lastError="pattern channel out of range!"; - ds.unload(); - delete[] file; - return false; - } - if (index<0 || index>(DIV_MAX_PATTERNS-1)) { - logE("pattern index out of range!",i); - lastError="pattern index out of range!"; - ds.unload(); - delete[] file; - return false; - } - if (subs<0 || subs>=(int)ds.subsong.size()) { - logE("pattern subsong out of range!",i); - lastError="pattern subsong out of range!"; - ds.unload(); - delete[] file; - return false; - } - - DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true); - pat->name=reader.readString(); - - // read new pattern - for (int j=0; jpatLen; j++) { - unsigned char mask=reader.readC(); - unsigned short effectMask=0; - - if (mask==0xff) break; - if (mask&128) { - j+=(mask&127)+1; - continue; - } - - if (mask&32) { - effectMask|=(unsigned char)reader.readC(); - } - if (mask&64) { - effectMask|=((unsigned short)reader.readC()&0xff)<<8; - } - if (mask&8) effectMask|=1; - if (mask&16) effectMask|=2; - - if (mask&1) { // note - unsigned char note=reader.readC(); - if (note==180) { - pat->data[j][0]=100; - pat->data[j][1]=0; - } else if (note==181) { - pat->data[j][0]=101; - pat->data[j][1]=0; - } else if (note==182) { - pat->data[j][0]=102; - pat->data[j][1]=0; - } else if (note<180) { - pat->data[j][0]=newFormatNotes[note]; - pat->data[j][1]=newFormatOctaves[note]; - } else { - pat->data[j][0]=0; - pat->data[j][1]=0; - } - } - if (mask&2) { // instrument - pat->data[j][2]=(unsigned char)reader.readC(); - } - if (mask&4) { // volume - pat->data[j][3]=(unsigned char)reader.readC(); - } - for (unsigned char k=0; k<16; k++) { - if (effectMask&(1<data[j][4+k]=(unsigned char)reader.readC(); - } - } - } - } else { - int chan=reader.readS(); - int index=reader.readS(); - int subs=0; - if (ds.version>=95) { - subs=reader.readS(); - } else { - reader.readS(); - } - reader.readS(); - - logD("- %d, %d, %d (old)",subs,chan,index); - - if (chan<0 || chan>=tchans) { - logE("pattern channel out of range!",i); - lastError="pattern channel out of range!"; - ds.unload(); - delete[] file; - return false; - } - if (index<0 || index>(DIV_MAX_PATTERNS-1)) { - logE("pattern index out of range!",i); - lastError="pattern index out of range!"; - ds.unload(); - delete[] file; - return false; - } - if (subs<0 || subs>=(int)ds.subsong.size()) { - logE("pattern subsong out of range!",i); - lastError="pattern subsong out of range!"; - ds.unload(); - delete[] file; - return false; - } - - DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true); - for (int j=0; jpatLen; j++) { - pat->data[j][0]=reader.readS(); - pat->data[j][1]=reader.readS(); - pat->data[j][2]=reader.readS(); - pat->data[j][3]=reader.readS(); - for (int k=0; kpat[chan].effectCols; k++) { - pat->data[j][4+(k<<1)]=reader.readS(); - pat->data[j][5+(k<<1)]=reader.readS(); - } - if (pat->data[j][0]==0 && pat->data[j][1]!=0) { - logD("what? %d:%d:%d note %d octave %d",chan,i,j,pat->data[j][0],pat->data[j][1]); - pat->data[j][0]=12; - pat->data[j][1]--; - } - } - - if (ds.version>=51) { - pat->name=reader.readString(); - } - } - } - - if (reader.tell()opnCount) { - for (DivInstrument* i: ds.ins) { - if (i->type==DIV_INS_FM) i->type=DIV_INS_OPM; - } - } - if (nesCount>snCount) { - for (DivInstrument* i: ds.ins) { - if (i->type==DIV_INS_STD) i->type=DIV_INS_NES; - } - } - } - - // ExtCh compat flag - for (int i=0; i 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); - ds.sample.reserve(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=(unsigned char)reader.readC(); - if (ds.subsong[0]->ordersLen<1 || ds.subsong[0]->ordersLen>128) { - 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]->chanShowChanOsc[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; - ds.subsong[0]->chanShowChanOsc[i]=false; - } - - // instrument creation - ds.ins.reserve(insCount); - 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 - ds.ins.reserve(ds.insLen); - 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); - seq.reserve(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..."); - ds.wave.reserve(80); - 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 - ds.wave.reserve(48); - 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; - ds.ins.reserve(64 - 5); - 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); - ds.subsong.reserve(totalSongs); - for (int i=0; i<=totalSongs; i++) { - String subSongName=reader.readString(); - ds.subsong.push_back(new DivSubSong); - ds.subsong[i]->name=subSongName; - ds.subsong[i]->hilightA=hilightA; - ds.subsong[i]->hilightB=hilightB; - if (customHz!=0) { - ds.subsong[i]->hz=customHz; - } - 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(); - - dir.reserve(numDirs); - 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]?1:0)| - (subSong->chanShowChanOsc[i]?2:0) - ); - } - - for (int i=0; iwriteC(subSong->chanCollapse[i]); - } - - for (int i=0; iwriteString(subSong->chanName[i],false); - } - - for (int i=0; iwriteString(subSong->chanShortName[i],false); - } - - w->writeString(song.notes,false); - - w->writeF(song.masterVol); - - // extended compat flags - w->writeC(song.brokenSpeedSel); - w->writeC(song.noSlidesOnFirstTick); - w->writeC(song.rowResetsArpPos); - w->writeC(song.ignoreJumpAtEnd); - w->writeC(song.buggyPortaAfterSlide); - w->writeC(song.gbInsAffectsEnvelope); - w->writeC(song.sharedExtStat); - w->writeC(song.ignoreDACModeOutsideIntendedChannel); - w->writeC(song.e1e2AlsoTakePriority); - w->writeC(song.newSegaPCM); - w->writeC(song.fbPortaPause); - w->writeC(song.snDutyReset); - w->writeC(song.pitchMacroIsLinear); - w->writeC(song.pitchSlideSpeed); - w->writeC(song.oldOctaveBoundary); - w->writeC(song.noOPN2Vol); - w->writeC(song.newVolumeScaling); - w->writeC(song.volMacroLinger); - w->writeC(song.brokenOutVol); - w->writeC(song.e1e2StopOnSameNote); - w->writeC(song.brokenPortaArp); - w->writeC(song.snNoLowPeriods); - w->writeC(song.delayBehavior); - w->writeC(song.jumpTreatment); - w->writeC(song.autoSystem); - w->writeC(song.disableSampleMacro); - w->writeC(song.brokenOutVol2); - w->writeC(song.oldArpStrategy); - - // first subsong virtual tempo - w->writeS(subSong->virtualTempoN); - w->writeS(subSong->virtualTempoD); - - // subsong list - w->writeString(subSong->name,false); - w->writeString(subSong->notes,false); - w->writeC((unsigned char)(song.subsong.size()-1)); - w->writeC(0); // reserved - w->writeC(0); - w->writeC(0); - subSongPtrSeek=w->tell(); - // subsong pointers (we'll seek here later) - for (size_t i=0; i<(song.subsong.size()-1); i++) { - w->writeI(0); - } - - // additional metadata - w->writeString(song.systemName,false); - w->writeString(song.category,false); - w->writeString(song.nameJ,false); - w->writeString(song.authorJ,false); - w->writeString(song.systemNameJ,false); - w->writeString(song.categoryJ,false); - - // system output config - for (int i=0; iwriteF(song.systemVol[i]); - w->writeF(song.systemPan[i]); - w->writeF(song.systemPanFR[i]); - } - w->writeI(song.patchbay.size()); - for (unsigned int i: song.patchbay) { - w->writeI(i); - } - w->writeC(song.patchbayAuto); - - // even more compat flags - w->writeC(song.brokenPortaLegato); - w->writeC(song.brokenFMOff); - w->writeC(song.preNoteNoEffect); - w->writeC(song.oldDPCM); - w->writeC(song.resetArpPhaseOnNewNote); - w->writeC(song.ceilVolumeScaling); - w->writeC(song.oldAlwaysSetVolume); - for (int i=0; i<1; i++) { - w->writeC(0); - } - - // speeds of first song - w->writeC(subSong->speeds.len); - for (int i=0; i<16; i++) { - w->writeC(subSong->speeds.val[i]); - } - - // groove list - w->writeC((unsigned char)song.grooves.size()); - for (const DivGroovePattern& i: song.grooves) { - w->writeC(i.len); - for (int j=0; j<16; j++) { - w->writeC(i.val[j]); - } - } - - // asset dir pointers (we'll seek here later) - assetDirPtrSeek=w->tell(); - w->writeI(0); - w->writeI(0); - w->writeI(0); - - blockEndSeek=w->tell(); - w->seek(blockStartSeek,SEEK_SET); - w->writeI(blockEndSeek-blockStartSeek-4); - w->seek(0,SEEK_END); - - /// SUBSONGS - subSongPtr.reserve(song.subsong.size() - 1); - for (subSongIndex=1; subSongIndextell()); - w->write("SONG",4); - blockStartSeek=w->tell(); - w->writeI(0); - - w->writeC(subSong->timeBase); - w->writeC(subSong->speeds.val[0]); - w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]); - w->writeC(subSong->arpLen); - w->writeF(subSong->hz); - w->writeS(subSong->patLen); - w->writeS(subSong->ordersLen); - w->writeC(subSong->hilightA); - w->writeC(subSong->hilightB); - w->writeS(subSong->virtualTempoN); - w->writeS(subSong->virtualTempoD); - - w->writeString(subSong->name,false); - w->writeString(subSong->notes,false); - - for (int i=0; iordersLen; j++) { - w->writeC(subSong->orders.ord[i][j]); - } - } - - for (int i=0; iwriteC(subSong->pat[i].effectCols); - } - - for (int i=0; iwriteC( - (subSong->chanShow[i]?1:0)| - (subSong->chanShowChanOsc[i]?2:0) - ); - } - - for (int i=0; iwriteC(subSong->chanCollapse[i]); - } - - for (int i=0; iwriteString(subSong->chanName[i],false); - } - - for (int i=0; iwriteString(subSong->chanShortName[i],false); - } - - // speeds - w->writeC(subSong->speeds.len); - for (int i=0; i<16; i++) { - w->writeC(subSong->speeds.val[i]); - } - - blockEndSeek=w->tell(); - w->seek(blockStartSeek,SEEK_SET); - w->writeI(blockEndSeek-blockStartSeek-4); - w->seek(0,SEEK_END); - } - - /// CHIP FLAGS - sysFlagsPtr.reserve(song.systemLen); - for (int i=0; itell()); - w->write("FLAG",4); - blockStartSeek=w->tell(); - w->writeI(0); - - w->writeString(data,false); - - blockEndSeek=w->tell(); - w->seek(blockStartSeek,SEEK_SET); - w->writeI(blockEndSeek-blockStartSeek-4); - w->seek(0,SEEK_END); - } - - /// ASSET DIRECTORIES - assetDirPtr[0]=w->tell(); - putAssetDirData(w,song.insDir); - assetDirPtr[1]=w->tell(); - putAssetDirData(w,song.waveDir); - assetDirPtr[2]=w->tell(); - putAssetDirData(w,song.sampleDir); - - /// INSTRUMENT - insPtr.reserve(song.insLen); - for (int i=0; itell()); - ins->putInsData2(w,false); - } - - /// WAVETABLE - wavePtr.reserve(song.waveLen); - for (int i=0; itell()); - wave->putWaveData(w); - } - - /// SAMPLE - samplePtr.reserve(song.sampleLen); - for (int i=0; itell()); - sample->putSampleData(w); - } - - /// PATTERN - patPtr.reserve(patsToWrite.size()); - for (PatToWrite& i: patsToWrite) { - DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false); - patPtr.push_back(w->tell()); - - if (newPatternFormat) { - w->write("PATN",4); - blockStartSeek=w->tell(); - w->writeI(0); - - w->writeC(i.subsong); - w->writeC(i.chan); - w->writeS(i.pat); - w->writeString(pat->name,false); - - unsigned char emptyRows=0; - - for (int j=0; jpatLen; j++) { - unsigned char mask=0; - unsigned char finalNote=255; - unsigned short effectMask=0; - - if (pat->data[j][0]==100) { - finalNote=180; - } else if (pat->data[j][0]==101) { // note release - finalNote=181; - } else if (pat->data[j][0]==102) { // macro release - finalNote=182; - } else if (pat->data[j][1]==0 && pat->data[j][0]==0) { - finalNote=255; - } else { - int seek=(pat->data[j][0]+(signed char)pat->data[j][1]*12)+60; - if (seek<0 || seek>=180) { - finalNote=255; - } else { - finalNote=seek; - } - } - - if (finalNote!=255) mask|=1; // note - if (pat->data[j][2]!=-1) mask|=2; // instrument - if (pat->data[j][3]!=-1) mask|=4; // volume - for (int k=0; kpat[i.chan].effectCols*2; k+=2) { - if (k==0) { - if (pat->data[j][4+k]!=-1) mask|=8; - if (pat->data[j][5+k]!=-1) mask|=16; - } else if (k<8) { - if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=32; - } else { - if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=64; - } - - if (pat->data[j][4+k]!=-1) effectMask|=(1<data[j][5+k]!=-1) effectMask|=(2<127) { - w->writeC(128|(emptyRows-2)); - emptyRows=0; - } - } else { - if (emptyRows>1) { - w->writeC(128|(emptyRows-2)); - emptyRows=0; - } else if (emptyRows) { - w->writeC(0); - emptyRows=0; - } - - w->writeC(mask); - - if (mask&32) w->writeC(effectMask&0xff); - if (mask&64) w->writeC((effectMask>>8)&0xff); - - if (mask&1) w->writeC(finalNote); - if (mask&2) w->writeC(pat->data[j][2]); - if (mask&4) w->writeC(pat->data[j][3]); - if (mask&8) w->writeC(pat->data[j][4]); - if (mask&16) w->writeC(pat->data[j][5]); - if (mask&32) { - if (effectMask&4) w->writeC(pat->data[j][6]); - if (effectMask&8) w->writeC(pat->data[j][7]); - if (effectMask&16) w->writeC(pat->data[j][8]); - if (effectMask&32) w->writeC(pat->data[j][9]); - if (effectMask&64) w->writeC(pat->data[j][10]); - if (effectMask&128) w->writeC(pat->data[j][11]); - } - if (mask&64) { - if (effectMask&256) w->writeC(pat->data[j][12]); - if (effectMask&512) w->writeC(pat->data[j][13]); - if (effectMask&1024) w->writeC(pat->data[j][14]); - if (effectMask&2048) w->writeC(pat->data[j][15]); - if (effectMask&4096) w->writeC(pat->data[j][16]); - if (effectMask&8192) w->writeC(pat->data[j][17]); - if (effectMask&16384) w->writeC(pat->data[j][18]); - if (effectMask&32768) w->writeC(pat->data[j][19]); - } - } - } - - // stop - w->writeC(0xff); - } else { - w->write("PATR",4); - blockStartSeek=w->tell(); - w->writeI(0); - - w->writeS(i.chan); - w->writeS(i.pat); - w->writeS(i.subsong); - - w->writeS(0); // reserved - - for (int j=0; jpatLen; j++) { - w->writeS(pat->data[j][0]); // note - w->writeS(pat->data[j][1]); // octave - w->writeS(pat->data[j][2]); // instrument - w->writeS(pat->data[j][3]); // volume -#ifdef TA_BIG_ENDIAN - for (int k=0; kpat[i.chan].effectCols*2; k++) { - w->writeS(pat->data[j][4+k]); - } -#else - w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects -#endif - } - - w->writeString(pat->name,false); - } - - blockEndSeek=w->tell(); - w->seek(blockStartSeek,SEEK_SET); - w->writeI(blockEndSeek-blockStartSeek-4); - w->seek(0,SEEK_END); - } - - /// POINTERS - w->seek(ptrSeek,SEEK_SET); - - for (int i=0; iwriteI(insPtr[i]); - } - - // wavetable pointers - for (int i=0; iwriteI(wavePtr[i]); - } - - // sample pointers - for (int i=0; iwriteI(samplePtr[i]); - } - - // pattern pointers - for (int i: patPtr) { - w->writeI(i); - } - - // subsong pointers - w->seek(subSongPtrSeek,SEEK_SET); - for (size_t i=0; i<(song.subsong.size()-1); i++) { - w->writeI(subSongPtr[i]); - } - - // flag pointers - w->seek(sysFlagsPtrSeek,SEEK_SET); - for (size_t i=0; iwriteI(sysFlagsPtr[i]); - } - - // asset dir pointers - w->seek(assetDirPtrSeek,SEEK_SET); - for (size_t i=0; i<3; i++) { - w->writeI(assetDirPtr[i]); - } - - saveLock.unlock(); - return w; -} - -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 - bool volIsCutoff=false; - - if (sys!=DIV_SYSTEM_GB) { - int realVolMacroLen=i->std.volMacro.len; - if (realVolMacroLen>127) realVolMacroLen=127; - if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { - if (i->std.algMacro.len>0) volIsCutoff=true; - if (volIsCutoff) { - if (i->std.volMacro.len>0) { - addWarning(".dmf only supports volume or cutoff macro in C64, but not both. volume macro will be lost."); - } - realVolMacroLen=i->std.algMacro.len; - if (realVolMacroLen>127) realVolMacroLen=127; - w->writeC(realVolMacroLen); - for (int j=0; jwriteI((-i->std.algMacro.val[j])+18); - } - } else { - w->writeC(realVolMacroLen); - for (int j=0; jwriteI(i->std.volMacro.val[j]); - } - } - } else { - w->writeC(realVolMacroLen); - for (int j=0; jwriteI(i->std.volMacro.val[j]); - } - } - if (realVolMacroLen>0) { - if (volIsCutoff) { - w->writeC(i->std.algMacro.loop); - } else { - w->writeC(i->std.volMacro.loop); - } - } - } - - bool arpMacroMode=false; - int arpMacroHowManyFixed=0; - int realArpMacroLen=i->std.arpMacro.len; - for (int j=0; jstd.arpMacro.len; j++) { - if ((i->std.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) { - arpMacroHowManyFixed++; - } - } - if (arpMacroHowManyFixed>=i->std.arpMacro.len-1) { - arpMacroMode=true; - } - if (i->std.arpMacro.len>0) { - if (arpMacroMode && i->std.arpMacro.val[i->std.arpMacro.len-1]==0 && i->std.arpMacro.loop>=i->std.arpMacro.len) { - realArpMacroLen--; - } - } - - if (realArpMacroLen>127) realArpMacroLen=127; - - w->writeC(realArpMacroLen); - - if (arpMacroMode) { - for (int j=0; jstd.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) { - w->writeI(i->std.arpMacro.val[j]^0x40000000); - } else { - w->writeI(i->std.arpMacro.val[j]); - } - } - } else { - for (int j=0; jstd.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) { - w->writeI((i->std.arpMacro.val[j]^0x40000000)+12); - } else { - w->writeI(i->std.arpMacro.val[j]+12); - } - } - } - if (realArpMacroLen>0) { - w->writeC(i->std.arpMacro.loop); - } - w->writeC(arpMacroMode); - - int realDutyMacroLen=i->std.dutyMacro.len; - if (realDutyMacroLen>127) realDutyMacroLen=127; - w->writeC(realDutyMacroLen); - if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { - for (int j=0; jwriteI(i->std.dutyMacro.val[j]+12); - } - } else { - for (int j=0; jwriteI(i->std.dutyMacro.val[j]); - } - } - if (realDutyMacroLen>0) { - w->writeC(i->std.dutyMacro.loop); - } - - int realWaveMacroLen=i->std.waveMacro.len; - if (realWaveMacroLen>127) realWaveMacroLen=127; - w->writeC(realWaveMacroLen); - for (int j=0; jwriteI(i->std.waveMacro.val[j]); - } - if (realWaveMacroLen>0) { - w->writeC(i->std.waveMacro.loop); - } - - if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { - w->writeC(i->c64.triOn); - w->writeC(i->c64.sawOn); - w->writeC(i->c64.pulseOn); - w->writeC(i->c64.noiseOn); - - w->writeC(i->c64.a); - w->writeC(i->c64.d); - w->writeC(i->c64.s); - w->writeC(i->c64.r); - - logW("duty and cutoff precision will be lost!"); - w->writeC((i->c64.duty*100)/4095); - - w->writeC(i->c64.ringMod); - w->writeC(i->c64.oscSync); - - w->writeC(i->c64.toFilter); - w->writeC(volIsCutoff); - w->writeC(i->c64.initFilter); - - w->writeC(i->c64.res); - w->writeC((i->c64.cut*100)/2047); - w->writeC(i->c64.hp); - w->writeC(i->c64.bp); - w->writeC(i->c64.lp); - w->writeC(i->c64.ch3off); - } - - if (sys==DIV_SYSTEM_GB) { - w->writeC(i->gb.envVol); - w->writeC(i->gb.envDir); - w->writeC(i->gb.envLen); - w->writeC(i->gb.soundLen); - } - } - } - - w->writeC(song.wave.size()); - for (DivWavetable* i: song.wave) { - w->writeI(i->len); - if (sys==DIV_SYSTEM_NES_FDS && version<26) { - for (int j=0; jlen; j++) { - w->writeI(i->data[j]>>2); - } - } else { - for (int j=0; jlen; j++) { - w->writeI(i->data[j]); - } - } - } - - bool relWarning=false; - - for (int i=0; iwriteC(curPat[i].effectCols); - - for (int j=0; jordersLen; j++) { - DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false); - for (int k=0; kpatLen; k++) { - if ((pat->data[k][0]==101 || pat->data[k][0]==102) && pat->data[k][1]==0) { - w->writeS(100); - w->writeS(0); - if (!relWarning) { - relWarning=true; - addWarning("note/macro release will be converted to note off!"); - } - } else { - w->writeS(pat->data[k][0]); // note - w->writeS(pat->data[k][1]); // octave - } - w->writeS(pat->data[k][3]); // volume -#ifdef TA_BIG_ENDIAN - for (int l=0; lwriteS(pat->data[k][4+l]); - } -#else - w->write(&pat->data[k][4],2*curPat[i].effectCols*2); // effects -#endif - w->writeS(pat->data[k][2]); // instrument - } - } - } - - if (song.sample.size()>0) { - addWarning("samples' rates will be rounded to nearest compatible value"); - } - - w->writeC(song.sample.size()); - for (DivSample* i: song.sample) { - w->writeI(i->samples); - w->writeString(i->name,true); - w->writeC(divToFileRate(i->rate)); - w->writeC(5); - w->writeC(50); - // i'm too lazy to deal with .dmf's weird way of storing 8-bit samples - w->writeC(16); - // well I can't be lazy if it's on a big-endian system -#ifdef TA_BIG_ENDIAN - for (unsigned int j=0; jlength16; j++) { - w->writeC(((unsigned short)i->data16[j])&0xff); - w->writeC(((unsigned short)i->data16[j])>>8); - } -#else - w->write(i->data16,i->length16); -#endif - } - - saveLock.unlock(); - return w; -} - -static const char* trueFalse[2]={ - "no", "yes" -}; - -static const char* gbEnvDir[2]={ - "down", "up" -}; - -static const char* notes[12]={ - "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" -}; - -static const char* notesNegative[12]={ - "c_", "c+", "d_", "d+", "e_", "f_", "f+", "g_", "g+", "a_", "a+", "b_" -}; - -static const char* sampleLoopModes[4]={ - "forward", "backward", "ping-pong", "invalid" -}; - -void writeTextMacro(SafeWriter* w, DivInstrumentMacro& m, const char* name, bool& wroteMacroHeader) { - if ((m.open&6)==0 && m.len<1) return; - if (!wroteMacroHeader) { - w->writeText("- macros:\n"); - wroteMacroHeader=true; - } - w->writeText(fmt::sprintf(" - %s:",name)); - int len=m.len; - switch (m.open&6) { - case 2: - len=16; - w->writeText(" [ADSR]"); - break; - case 4: - len=16; - w->writeText(" [LFO]"); - break; - } - if (m.mode) { - w->writeText(fmt::sprintf(" [MODE %d]",m.mode)); - } - if (m.delay>0) { - w->writeText(fmt::sprintf(" [DELAY %d]",m.delay)); - } - if (m.speed>1) { - w->writeText(fmt::sprintf(" [SPEED %d]",m.speed)); - } - for (int i=0; iwriteText(" |"); - } - if (i==m.rel) { - w->writeText(" /"); - } - w->writeText(fmt::sprintf(" %d",m.val[i])); - } - w->writeText("\n"); -} - -SafeWriter* DivEngine::saveText(bool separatePatterns) { - saveLock.lock(); - - SafeWriter* w=new SafeWriter; - w->init(); - - w->writeText(fmt::sprintf("# Furnace Text Export\n\ngenerated by Furnace %s (%d)\n\n# Song Information\n\n",DIV_VERSION,DIV_ENGINE_VERSION)); - w->writeText(fmt::sprintf("- name: %s\n",song.name)); - w->writeText(fmt::sprintf("- author: %s\n",song.author)); - w->writeText(fmt::sprintf("- album: %s\n",song.category)); - w->writeText(fmt::sprintf("- system: %s\n",song.systemName)); - w->writeText(fmt::sprintf("- tuning: %g\n\n",song.tuning)); - - w->writeText(fmt::sprintf("- instruments: %d\n",song.insLen)); - w->writeText(fmt::sprintf("- wavetables: %d\n",song.waveLen)); - w->writeText(fmt::sprintf("- samples: %d\n\n",song.sampleLen)); - - w->writeText("# Sound Chips\n\n"); - - for (int i=0; iwriteText(fmt::sprintf("- %s\n",getSystemName(song.system[i]))); - w->writeText(fmt::sprintf(" - id: %.2X\n",(int)song.system[i])); - w->writeText(fmt::sprintf(" - volume: %g\n",song.systemVol[i])); - w->writeText(fmt::sprintf(" - panning: %g\n",song.systemPan[i])); - w->writeText(fmt::sprintf(" - front/rear: %g\n",song.systemPanFR[i])); - - String sysFlags=song.systemFlags[i].toString(); - - if (!sysFlags.empty()) { - w->writeText(fmt::sprintf(" - flags:\n```\n%s\n```\n",sysFlags)); - } - } - - if (!song.notes.empty()) { - w->writeText("\n# Song Comments\n\n"); - w->writeText(song.notes); - } - - w->writeText("\n# Instruments\n\n"); - - for (int i=0; iwriteText(fmt::sprintf("## %.2X: %s\n\n",i,ins->name)); - - w->writeText(fmt::sprintf("- type: %d\n",(int)ins->type)); - - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM || ins->type==DIV_INS_ESFM) { - w->writeText("- FM parameters:\n"); - w->writeText(fmt::sprintf(" - ALG: %d\n",ins->fm.alg)); - w->writeText(fmt::sprintf(" - FB: %d\n",ins->fm.fb)); - w->writeText(fmt::sprintf(" - FMS: %d\n",ins->fm.fms)); - w->writeText(fmt::sprintf(" - AMS: %d\n",ins->fm.ams)); - w->writeText(fmt::sprintf(" - FMS2: %d\n",ins->fm.fms2)); - w->writeText(fmt::sprintf(" - AMS2: %d\n",ins->fm.ams2)); - w->writeText(fmt::sprintf(" - operators: %d\n",ins->fm.ops)); - w->writeText(fmt::sprintf(" - OPLL patch: %d\n",ins->fm.opllPreset)); - w->writeText(fmt::sprintf(" - fixed drum freq: %s\n",trueFalse[ins->fm.fixedDrums?1:0])); - w->writeText(fmt::sprintf(" - kick freq: %.4X\n",ins->fm.kickFreq)); - w->writeText(fmt::sprintf(" - snare/hat freq: %.4X\n",ins->fm.snareHatFreq)); - w->writeText(fmt::sprintf(" - tom/top freq: %.4X\n",ins->fm.tomTopFreq)); - - for (int j=0; jfm.ops; j++) { - DivInstrumentFM::Operator& op=ins->fm.op[j]; - - w->writeText(fmt::sprintf(" - operator %d:\n",j)); - w->writeText(fmt::sprintf(" - enabled: %s\n",trueFalse[op.enable?1:0])); - w->writeText(fmt::sprintf(" - AM: %d\n",op.am)); - w->writeText(fmt::sprintf(" - AR: %d\n",op.ar)); - w->writeText(fmt::sprintf(" - DR: %d\n",op.dr)); - w->writeText(fmt::sprintf(" - MULT: %d\n",op.mult)); - w->writeText(fmt::sprintf(" - RR: %d\n",op.rr)); - w->writeText(fmt::sprintf(" - SL: %d\n",op.sl)); - w->writeText(fmt::sprintf(" - TL: %d\n",op.tl)); - w->writeText(fmt::sprintf(" - DT2: %d\n",op.dt2)); - w->writeText(fmt::sprintf(" - RS: %d\n",op.rs)); - w->writeText(fmt::sprintf(" - DT: %d\n",op.dt)); - w->writeText(fmt::sprintf(" - D2R: %d\n",op.d2r)); - w->writeText(fmt::sprintf(" - SSG-EG: %d\n",op.ssgEnv)); - w->writeText(fmt::sprintf(" - DAM: %d\n",op.dam)); - w->writeText(fmt::sprintf(" - DVB: %d\n",op.dvb)); - w->writeText(fmt::sprintf(" - EGT: %d\n",op.egt)); - w->writeText(fmt::sprintf(" - KSL: %d\n",op.ksl)); - w->writeText(fmt::sprintf(" - SUS: %d\n",op.sus)); - w->writeText(fmt::sprintf(" - VIB: %d\n",op.vib)); - w->writeText(fmt::sprintf(" - WS: %d\n",op.ws)); - w->writeText(fmt::sprintf(" - KSR: %d\n",op.ksr)); - w->writeText(fmt::sprintf(" - TL volume scale: %d\n",op.kvs)); - } - } - - if (ins->type==DIV_INS_ESFM) { - w->writeText("- ESFM parameters:\n"); - w->writeText(fmt::sprintf(" - noise mode: %d\n",ins->esfm.noise)); - - for (int j=0; jfm.ops; j++) { - DivInstrumentESFM::Operator& opE=ins->esfm.op[j]; - - w->writeText(fmt::sprintf(" - operator %d:\n",j)); - w->writeText(fmt::sprintf(" - DL: %d\n",opE.delay)); - w->writeText(fmt::sprintf(" - OL: %d\n",opE.outLvl)); - w->writeText(fmt::sprintf(" - MI: %d\n",opE.modIn)); - w->writeText(fmt::sprintf(" - output left: %s\n",trueFalse[opE.left?1:0])); - w->writeText(fmt::sprintf(" - output right: %s\n",trueFalse[opE.right?1:0])); - w->writeText(fmt::sprintf(" - CT: %d\n",opE.ct)); - w->writeText(fmt::sprintf(" - DT: %d\n",opE.dt)); - w->writeText(fmt::sprintf(" - fixed frequency: %s\n",trueFalse[opE.fixed?1:0])); - } - } - - if (ins->type==DIV_INS_GB) { - w->writeText("- Game Boy parameters:\n"); - w->writeText(fmt::sprintf(" - volume: %d\n",ins->gb.envVol)); - w->writeText(fmt::sprintf(" - direction: %s\n",gbEnvDir[ins->gb.envDir?1:0])); - w->writeText(fmt::sprintf(" - length: %d\n",ins->gb.envLen)); - w->writeText(fmt::sprintf(" - sound length: %d\n",ins->gb.soundLen)); - w->writeText(fmt::sprintf(" - use software envelope: %s\n",trueFalse[ins->gb.softEnv?1:0])); - w->writeText(fmt::sprintf(" - always initialize: %s\n",trueFalse[ins->gb.softEnv?1:0])); - if (ins->gb.hwSeqLen>0) { - w->writeText(" - hardware sequence:\n"); - for (int j=0; jgb.hwSeqLen; j++) { - w->writeText(fmt::sprintf(" - %d: %.2X %.4X\n",j,ins->gb.hwSeq[j].cmd,ins->gb.hwSeq[j].data)); - } - } - } - - bool header=false; - writeTextMacro(w,ins->std.volMacro,"vol",header); - writeTextMacro(w,ins->std.arpMacro,"arp",header); - writeTextMacro(w,ins->std.dutyMacro,"duty",header); - writeTextMacro(w,ins->std.waveMacro,"wave",header); - writeTextMacro(w,ins->std.pitchMacro,"pitch",header); - writeTextMacro(w,ins->std.panLMacro,"panL",header); - writeTextMacro(w,ins->std.panRMacro,"panR",header); - writeTextMacro(w,ins->std.phaseResetMacro,"phaseReset",header); - writeTextMacro(w,ins->std.ex1Macro,"ex1",header); - writeTextMacro(w,ins->std.ex2Macro,"ex2",header); - writeTextMacro(w,ins->std.ex3Macro,"ex3",header); - writeTextMacro(w,ins->std.ex4Macro,"ex4",header); - writeTextMacro(w,ins->std.ex5Macro,"ex5",header); - writeTextMacro(w,ins->std.ex6Macro,"ex6",header); - writeTextMacro(w,ins->std.ex7Macro,"ex7",header); - writeTextMacro(w,ins->std.ex8Macro,"ex8",header); - writeTextMacro(w,ins->std.algMacro,"alg",header); - writeTextMacro(w,ins->std.fbMacro,"fb",header); - writeTextMacro(w,ins->std.fmsMacro,"fms",header); - writeTextMacro(w,ins->std.amsMacro,"ams",header); - - // TODO: the rest - w->writeText("\n"); - } - - w->writeText("\n# Wavetables\n\n"); - - for (int i=0; iwriteText(fmt::sprintf("- %d (%dx%d):",i,wave->len+1,wave->max+1)); - for (int j=0; j<=wave->len; j++) { - w->writeText(fmt::sprintf(" %d",wave->data[j])); - } - w->writeText("\n"); - } - - w->writeText("\n# Samples\n\n"); - - for (int i=0; iwriteText(fmt::sprintf("## %.2X: %s\n\n",i,sample->name)); - - w->writeText(fmt::sprintf("- format: %d\n",(int)sample->depth)); - w->writeText(fmt::sprintf("- data length: %d\n",sample->getCurBufLen())); - w->writeText(fmt::sprintf("- samples: %d\n",sample->samples)); - w->writeText(fmt::sprintf("- rate: %d\n",sample->centerRate)); - w->writeText(fmt::sprintf("- compat rate: %d\n",sample->rate)); - w->writeText(fmt::sprintf("- loop: %s\n",trueFalse[sample->loop?1:0])); - if (sample->loop) { - w->writeText(fmt::sprintf(" - start: %d\n",sample->loopStart)); - w->writeText(fmt::sprintf(" - end: %d\n",sample->loopEnd)); - w->writeText(fmt::sprintf(" - mode: %s\n",sampleLoopModes[sample->loopMode&3])); - } - w->writeText(fmt::sprintf("- BRR emphasis: %s\n",trueFalse[sample->brrEmphasis?1:0])); - w->writeText(fmt::sprintf("- dither: %s\n",trueFalse[sample->dither?1:0])); - - // TODO' render matrix - unsigned char* buf=(unsigned char*)sample->getCurBuf(); - unsigned int bufLen=sample->getCurBufLen(); - w->writeText("\n```"); - for (unsigned int i=0; iwriteText(fmt::sprintf("\n%.8X:",i)); - w->writeText(fmt::sprintf(" %.2X",buf[i])); - } - w->writeText("\n```\n\n"); - } - - w->writeText("\n# Subsongs\n\n"); - - for (size_t i=0; iwriteText(fmt::sprintf("## %d: %s\n\n",(int)i,s->name)); - - w->writeText(fmt::sprintf("- tick rate: %g\n",s->hz)); - w->writeText(fmt::sprintf("- speeds:")); - for (int j=0; jspeeds.len; j++) { - w->writeText(fmt::sprintf(" %d",s->speeds.val[j])); - } - w->writeText("\n"); - w->writeText(fmt::sprintf("- virtual tempo: %d/%d\n",s->virtualTempoN,s->virtualTempoD)); - w->writeText(fmt::sprintf("- time base: %d\n",s->timeBase)); - w->writeText(fmt::sprintf("- pattern length: %d\n",s->patLen)); - w->writeText(fmt::sprintf("\norders:\n```\n")); - - for (int j=0; jordersLen; j++) { - w->writeText(fmt::sprintf("%.2X |",j)); - for (int k=0; kwriteText(fmt::sprintf(" %.2X",s->orders.ord[k][j])); - } - w->writeText("\n"); - } - w->writeText("```\n\n## Patterns\n\n"); - - if (separatePatterns) { - w->writeText("TODO: separate patterns\n\n"); - } else { - for (int j=0; jordersLen; j++) { - w->writeText(fmt::sprintf("----- ORDER %.2X\n",j)); - - for (int k=0; kpatLen; k++) { - w->writeText(fmt::sprintf("%.2X ",k)); - - for (int l=0; lpat[l].getPattern(s->orders.ord[l][j],false); - - int note=p->data[k][0]; - int octave=p->data[k][1]; - - if (note==0 && octave==0) { - w->writeText("|... "); - } else if (note==100) { - w->writeText("|OFF "); - } else if (note==101) { - w->writeText("|=== "); - } else if (note==102) { - w->writeText("|REL "); - } else if ((octave>9 && octave<250) || note>12) { - w->writeText("|??? "); - } else { - if (octave>=128) octave-=256; - if (note>11) { - note-=12; - octave++; - } - w->writeText(fmt::sprintf("|%s%d ",(octave<0)?notesNegative[note]:notes[note],(octave<0)?(-octave):octave)); - } - - if (p->data[k][2]==-1) { - w->writeText(".. "); - } else { - w->writeText(fmt::sprintf("%.2X ",p->data[k][2]&0xff)); - } - - if (p->data[k][3]==-1) { - w->writeText(".."); - } else { - w->writeText(fmt::sprintf("%.2X",p->data[k][3]&0xff)); - } - - for (int m=0; mpat[l].effectCols; m++) { - if (p->data[k][4+(m<<1)]==-1) { - w->writeText(" .."); - } else { - w->writeText(fmt::sprintf(" %.2X",p->data[k][4+(m<<1)]&0xff)); - } - if (p->data[k][5+(m<<1)]==-1) { - w->writeText(".."); - } else { - w->writeText(fmt::sprintf("%.2X",p->data[k][5+(m<<1)]&0xff)); - } - } - } - - w->writeText("\n"); - } - } - } - - } - - saveLock.unlock(); - return w; -} diff --git a/src/engine/fileOps/dmf.cpp b/src/engine/fileOps/dmf.cpp new file mode 100644 index 000000000..fda0c27d8 --- /dev/null +++ b/src/engine/fileOps/dmf.cpp @@ -0,0 +1,1906 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "fileOpsCommon.h" + +static double samplePitches[11]={ + 0.1666666666, 0.2, 0.25, 0.333333333, 0.5, + 1, + 2, 3, 4, 5, 6 +}; + +bool DivEngine::loadDMF(unsigned char* file, size_t len) { + SafeReader reader=SafeReader(file,len); + warnings=""; + try { + DivSong ds; + unsigned char historicColIns[DIV_MAX_CHANS]; + for (int i=0; i0x1b) { + logE("this version is not supported by Furnace yet!"); + lastError="this version is not supported by Furnace yet"; + delete[] file; + return false; + } + unsigned char sys=0; + ds.systemLen=1; + if (ds.version<0x09) { + // V E R S I O N -> 3 <- + // AWESOME + ds.system[0]=DIV_SYSTEM_YMU759; + } else { + sys=reader.readC(); + ds.system[0]=systemFromFileDMF(sys); + } + if (ds.system[0]==DIV_SYSTEM_NULL) { + logE("invalid system 0x%.2x!",sys); + lastError="system not supported. running old version?"; + delete[] file; + return false; + } + + if (ds.system[0]==DIV_SYSTEM_YMU759 && ds.version<0x10) { + ds.vendor=reader.readString((unsigned char)reader.readC()); + ds.carrier=reader.readString((unsigned char)reader.readC()); + ds.category=reader.readString((unsigned char)reader.readC()); + ds.name=reader.readString((unsigned char)reader.readC()); + ds.author=reader.readString((unsigned char)reader.readC()); + ds.writer=reader.readString((unsigned char)reader.readC()); + ds.composer=reader.readString((unsigned char)reader.readC()); + ds.arranger=reader.readString((unsigned char)reader.readC()); + ds.copyright=reader.readString((unsigned char)reader.readC()); + ds.manGroup=reader.readString((unsigned char)reader.readC()); + ds.manInfo=reader.readString((unsigned char)reader.readC()); + ds.createdDate=reader.readString((unsigned char)reader.readC()); + ds.revisionDate=reader.readString((unsigned char)reader.readC()); + logI("%s by %s",ds.name.c_str(),ds.author.c_str()); + logI("has YMU-specific data:"); + logI("- carrier: %s",ds.carrier.c_str()); + logI("- category: %s",ds.category.c_str()); + logI("- vendor: %s",ds.vendor.c_str()); + logI("- writer: %s",ds.writer.c_str()); + logI("- composer: %s",ds.composer.c_str()); + logI("- arranger: %s",ds.arranger.c_str()); + logI("- copyright: %s",ds.copyright.c_str()); + logI("- management group: %s",ds.manGroup.c_str()); + logI("- management info: %s",ds.manInfo.c_str()); + logI("- created on: %s",ds.createdDate.c_str()); + logI("- revision date: %s",ds.revisionDate.c_str()); + } else { + ds.name=reader.readString((unsigned char)reader.readC()); + ds.author=reader.readString((unsigned char)reader.readC()); + logI("%s by %s",ds.name.c_str(),ds.author.c_str()); + } + + // compatibility flags + if (!getConfInt("noDMFCompat",0)) { + ds.limitSlides=true; + ds.linearPitch=1; + ds.loopModality=0; + ds.properNoiseLayout=false; + ds.waveDutyIsVol=false; + // TODO: WHAT?! geodude.dmf fails when this is true + // but isn't that how Defle behaves??? + ds.resetMacroOnPorta=false; + ds.legacyVolumeSlides=true; + ds.compatibleArpeggio=true; + ds.noteOffResetsSlides=true; + ds.targetResetsSlides=true; + ds.arpNonPorta=false; + ds.algMacroBehavior=false; + ds.brokenShortcutSlides=false; + ds.ignoreDuplicateSlides=true; + ds.brokenDACMode=true; + ds.oneTickCut=false; + ds.newInsTriggersInPorta=true; + ds.arp0Reset=true; + ds.brokenSpeedSel=true; + ds.noSlidesOnFirstTick=false; + ds.rowResetsArpPos=false; + ds.ignoreJumpAtEnd=true; + ds.buggyPortaAfterSlide=true; + ds.gbInsAffectsEnvelope=true; + ds.ignoreDACModeOutsideIntendedChannel=false; + ds.e1e2AlsoTakePriority=true; + ds.fbPortaPause=true; + ds.snDutyReset=true; + ds.oldOctaveBoundary=false; + ds.noOPN2Vol=true; + ds.newVolumeScaling=false; + ds.volMacroLinger=false; + ds.brokenOutVol=true; + ds.brokenOutVol2=true; + ds.e1e2StopOnSameNote=true; + ds.brokenPortaArp=false; + ds.snNoLowPeriods=true; + ds.disableSampleMacro=true; + ds.preNoteNoEffect=true; + ds.oldDPCM=true; + ds.delayBehavior=0; + ds.jumpTreatment=2; + ds.oldAlwaysSetVolume=true; + + // 1.1 compat flags + if (ds.version>24) { + ds.waveDutyIsVol=true; + ds.legacyVolumeSlides=false; + } + + // Neo Geo detune is caused by Defle running Neo Geo at the wrong clock. + /* + if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT + || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT + || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { + ds.tuning=443.23; + } + */ + + // Genesis detuned on Defle v10 and earlier + /*if (ds.version<19 && ds.system[0]==DIV_SYSTEM_GENESIS) { + ds.tuning=443.23; + }*/ + // C64 detuned on Defle v11 and earlier + /*if (ds.version<21 && (ds.system[0]==DIV_SYSTEM_C64_6581 || ds.system[0]==DIV_SYSTEM_C64_8580)) { + ds.tuning=433.2; + }*/ + + // Game Boy arp+soundLen screwery + if (ds.system[0]==DIV_SYSTEM_GB) { + ds.systemFlags[0].set("enoughAlready",true); + } + } + + logI("reading module data..."); + if (ds.version>0x0c) { + ds.subsong[0]->hilightA=reader.readC(); + ds.subsong[0]->hilightB=reader.readC(); + } + + bool customTempo=false; + + ds.subsong[0]->timeBase=reader.readC(); + ds.subsong[0]->speeds.len=2; + ds.subsong[0]->speeds.val[0]=reader.readC(); + if (ds.version>0x07) { + ds.subsong[0]->speeds.val[1]=reader.readC(); + bool pal=reader.readC(); + ds.subsong[0]->hz=pal?60:50; + customTempo=reader.readC(); + } else { + ds.subsong[0]->speeds.len=1; + } + if (ds.version>0x0a) { + String hz=reader.readString(3); + if (customTempo) { + try { + ds.subsong[0]->hz=std::stoi(hz); + } catch (std::exception& e) { + logW("invalid custom Hz!"); + ds.subsong[0]->hz=60; + } + } + } + if (ds.version>0x17) { + ds.subsong[0]->patLen=reader.readI(); + } else { + ds.subsong[0]->patLen=(unsigned char)reader.readC(); + } + ds.subsong[0]->ordersLen=(unsigned char)reader.readC(); + + if (ds.subsong[0]->patLen<0) { + logE("pattern length is negative!"); + lastError="pattern lengrh is negative!"; + delete[] file; + return false; + } + if (ds.subsong[0]->patLen>256) { + logE("pattern length is too large!"); + lastError="pattern length is too large!"; + delete[] file; + return false; + } + if (ds.subsong[0]->ordersLen<0) { + logE("song length is negative!"); + lastError="song length is negative!"; + delete[] file; + return false; + } + if (ds.subsong[0]->ordersLen>127) { + logE("song is too long!"); + lastError="song is too long!"; + delete[] file; + return false; + } + + if (ds.version<20 && ds.version>3) { + ds.subsong[0]->arpLen=reader.readC(); + } else { + ds.subsong[0]->arpLen=1; + } + + if (ds.system[0]==DIV_SYSTEM_YMU759) { + switch (ds.subsong[0]->timeBase) { + case 0: + ds.subsong[0]->hz=248; + break; + case 1: + ds.subsong[0]->hz=200; + break; + case 2: + ds.subsong[0]->hz=100; + break; + case 3: + ds.subsong[0]->hz=50; + break; + case 4: + ds.subsong[0]->hz=25; + break; + case 5: + ds.subsong[0]->hz=20; + break; + default: + ds.subsong[0]->hz=248; + break; + } + ds.subsong[0]->timeBase=0; + addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system."); + } + + logV("%x",reader.tell()); + + logI("reading pattern matrix (%d * %d = %d)...",ds.subsong[0]->ordersLen,getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen*getChannelCount(ds.system[0])); + for (int i=0; iordersLen; j++) { + ds.subsong[0]->orders.ord[i][j]=reader.readC(); + if (ds.subsong[0]->orders.ord[i][j]>0x7f) { + logE("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]); + lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]); + delete[] file; + return false; + } + if (ds.version>0x18) { // 1.1 pattern names + ds.subsong[0]->pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC()); + } + } + if (ds.version>0x03 && ds.version<0x06 && i<16) { + historicColIns[i]=reader.readC(); + } + } + + logV("%x",reader.tell()); + + if (ds.version>0x05) { + ds.insLen=(unsigned char)reader.readC(); + } else { + ds.insLen=16; + } + logI("reading instruments (%d)...",ds.insLen); + if (ds.insLen>0) ds.ins.reserve(ds.insLen); + for (int i=0; i0x05) { + ins->name=reader.readString((unsigned char)reader.readC()); + } + logD("%d name: %s",i,ins->name.c_str()); + if (ds.version<0x0b) { + // instruments in ancient versions were all FM. + mode=1; + } else { + mode=reader.readC(); + if (mode>1) logW("%d: invalid instrument mode %d!",i,mode); + } + ins->type=mode?DIV_INS_FM:DIV_INS_STD; + if (ds.system[0]==DIV_SYSTEM_GB) { + ins->type=DIV_INS_GB; + } + if (ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) { + ins->type=DIV_INS_C64; + } + if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT + || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT + || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { + if (!mode) { + ins->type=DIV_INS_AY; + } + } + if (ds.system[0]==DIV_SYSTEM_PCE) { + ins->type=DIV_INS_PCE; + } + if ((ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) && ins->type==DIV_INS_FM) { + ins->type=DIV_INS_OPLL; + } + if (ds.system[0]==DIV_SYSTEM_YMU759) { + ins->type=DIV_INS_OPL; + } + if (ds.system[0]==DIV_SYSTEM_ARCADE) { + ins->type=DIV_INS_OPM; + } + if ((ds.system[0]==DIV_SYSTEM_NES || ds.system[0]==DIV_SYSTEM_NES_VRC7 || ds.system[0]==DIV_SYSTEM_NES_FDS) && ins->type==DIV_INS_STD) { + ins->type=DIV_INS_NES; + } + + if (mode) { // FM + if (ds.version>0x05) { + ins->fm.alg=reader.readC(); + if (ds.version<0x13) { + reader.readC(); + } + ins->fm.fb=reader.readC(); + if (ds.version<0x13) { + reader.readC(); + } + ins->fm.fms=reader.readC(); + if (ds.version<0x13) { + reader.readC(); + ins->fm.ops=2+reader.readC()*2; + if (ds.system[0]!=DIV_SYSTEM_YMU759) ins->fm.ops=4; + } else { + ins->fm.ops=4; + } + ins->fm.ams=reader.readC(); + } else { + ins->fm.alg=reader.readC(); + reader.readC(); + ins->fm.fb=reader.readC(); + reader.readC(); // apparently an index of sorts starting from 0x59? + ins->fm.fms=reader.readC(); + reader.readC(); // 0x59+index? + ins->fm.ops=2+reader.readC()*2; + } + + logD("ALG %d FB %d FMS %d AMS %d OPS %d",ins->fm.alg,ins->fm.fb,ins->fm.fms,ins->fm.ams,ins->fm.ops); + if (ins->fm.ops!=2 && ins->fm.ops!=4) { + logE("invalid op count %d. did we read it wrong?",ins->fm.ops); + lastError="file is corrupt or unreadable at operators"; + delete[] file; + return false; + } + + for (int j=0; jfm.ops; j++) { + ins->fm.op[j].am=reader.readC(); + ins->fm.op[j].ar=reader.readC(); + if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { + ins->fm.op[j].ar&=15; + } + if (ds.version<0x13) { + ins->fm.op[j].dam=reader.readC(); + } + ins->fm.op[j].dr=reader.readC(); + if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { + ins->fm.op[j].dr&=15; + } + if (ds.version<0x13) { + ins->fm.op[j].dvb=reader.readC(); + ins->fm.op[j].egt=reader.readC(); + ins->fm.op[j].ksl=reader.readC(); + if (ds.version<0x11) { // don't know when did this change + ins->fm.op[j].ksr=reader.readC(); + } + } + ins->fm.op[j].mult=reader.readC(); + ins->fm.op[j].rr=reader.readC(); + ins->fm.op[j].sl=reader.readC(); + if (ds.version<0x13) { + ins->fm.op[j].sus=reader.readC(); + } + ins->fm.op[j].tl=reader.readC(); + if (ds.version<0x13) { + ins->fm.op[j].vib=reader.readC(); + ins->fm.op[j].ws=reader.readC(); + } else { + if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { + if (j==0) { + ins->fm.opllPreset=reader.readC(); + } else { + reader.readC(); + } + } else { + ins->fm.op[j].dt2=reader.readC(); + } + } + if (ds.version>0x05) { + if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { + ins->fm.op[j].ksr=reader.readC(); + ins->fm.op[j].vib=reader.readC(); + ins->fm.op[j].ksl=reader.readC(); + ins->fm.op[j].ssgEnv=reader.readC(); + } else { + ins->fm.op[j].rs=reader.readC(); + ins->fm.op[j].dt=reader.readC(); + ins->fm.op[j].d2r=reader.readC(); + ins->fm.op[j].ssgEnv=reader.readC(); + } + } + if (ds.version<0x12) { // before version 10 all ops were responsive to volume + ins->fm.op[j].kvs=1; + } + + logD("OP%d: AM %d AR %d DAM %d DR %d DVB %d EGT %d KSL %d MULT %d RR %d SL %d SUS %d TL %d VIB %d WS %d RS %d DT %d D2R %d SSG-EG %d",j, + ins->fm.op[j].am, + ins->fm.op[j].ar, + ins->fm.op[j].dam, + ins->fm.op[j].dr, + ins->fm.op[j].dvb, + ins->fm.op[j].egt, + ins->fm.op[j].ksl, + ins->fm.op[j].mult, + ins->fm.op[j].rr, + ins->fm.op[j].sl, + ins->fm.op[j].sus, + ins->fm.op[j].tl, + ins->fm.op[j].vib, + ins->fm.op[j].ws, + ins->fm.op[j].rs, + ins->fm.op[j].dt, + ins->fm.op[j].d2r, + ins->fm.op[j].ssgEnv + ); + } + + // swap alg operator 2 and 3 if YMU759 + if (ds.system[0]==DIV_SYSTEM_YMU759 && ins->fm.ops==4) { + DivInstrumentFM::Operator oldOp=ins->fm.op[2]; + ins->fm.op[2]=ins->fm.op[1]; + ins->fm.op[1]=oldOp; + + if (ins->fm.alg==1) { + ins->fm.alg=2; + } else if (ins->fm.alg==2) { + ins->fm.alg=1; + } + } + } else { // STD + if (ds.system[0]!=DIV_SYSTEM_GB || ds.version<0x12) { + ins->std.volMacro.len=reader.readC(); + for (int j=0; jstd.volMacro.len; j++) { + if (ds.version<0x0e) { + ins->std.volMacro.val[j]=reader.readC(); + } else { + ins->std.volMacro.val[j]=reader.readI(); + } + } + if (ins->std.volMacro.len>0) { + ins->std.volMacro.open=true; + ins->std.volMacro.loop=reader.readC(); + } else { + ins->std.volMacro.open=false; + } + } + + ins->std.arpMacro.len=reader.readC(); + for (int j=0; jstd.arpMacro.len; j++) { + if (ds.version<0x0e) { + ins->std.arpMacro.val[j]=reader.readC(); + } else { + ins->std.arpMacro.val[j]=reader.readI(); + } + } + if (ins->std.arpMacro.len>0) { + ins->std.arpMacro.loop=reader.readC(); + ins->std.arpMacro.open=true; + } else { + ins->std.arpMacro.open=false; + } + if (ds.version>0x0f) { + ins->std.arpMacro.mode=reader.readC(); + } + if (!ins->std.arpMacro.mode) { + for (int j=0; jstd.arpMacro.len; j++) { + ins->std.arpMacro.val[j]-=12; + } + } else { + ins->std.arpMacro.mode=0; + for (int j=0; jstd.arpMacro.len; j++) { + ins->std.arpMacro.val[j]^=0x40000000; + } + if (ins->std.arpMacro.loop==255 && ins->std.arpMacro.len<255) { + ins->std.arpMacro.val[ins->std.arpMacro.len++]=0; + } + } + + ins->std.dutyMacro.len=reader.readC(); + for (int j=0; jstd.dutyMacro.len; j++) { + if (ds.version<0x0e) { + ins->std.dutyMacro.val[j]=reader.readC(); + } else { + ins->std.dutyMacro.val[j]=reader.readI(); + } + /*if ((ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) && ins->std.dutyMacro.val[j]>24) { + ins->std.dutyMacro.val[j]=24; + }*/ + } + if (ins->std.dutyMacro.len>0) { + ins->std.dutyMacro.open=true; + ins->std.dutyMacro.loop=reader.readC(); + } else { + ins->std.dutyMacro.open=false; + } + + ins->std.waveMacro.len=reader.readC(); + for (int j=0; jstd.waveMacro.len; j++) { + if (ds.version<0x0e) { + ins->std.waveMacro.val[j]=reader.readC(); + } else { + ins->std.waveMacro.val[j]=reader.readI(); + } + } + if (ins->std.waveMacro.len>0) { + ins->std.waveMacro.open=true; + ins->std.waveMacro.loop=reader.readC(); + } else { + ins->std.waveMacro.open=false; + } + + if (ds.system[0]==DIV_SYSTEM_C64_6581 || ds.system[0]==DIV_SYSTEM_C64_8580) { + bool volIsCutoff=false; + + ins->c64.triOn=reader.readC(); + ins->c64.sawOn=reader.readC(); + ins->c64.pulseOn=reader.readC(); + ins->c64.noiseOn=reader.readC(); + + ins->c64.a=reader.readC(); + ins->c64.d=reader.readC(); + ins->c64.s=reader.readC(); + ins->c64.r=reader.readC(); + + ins->c64.duty=(reader.readC()*4095)/100; + + ins->c64.ringMod=reader.readC(); + ins->c64.oscSync=reader.readC(); + ins->c64.toFilter=reader.readC(); + if (ds.version<0x11) { + volIsCutoff=reader.readI(); + } else { + volIsCutoff=reader.readC(); + } + ins->c64.initFilter=reader.readC(); + + ins->c64.res=reader.readC(); + ins->c64.cut=(reader.readC()*2047)/100; + ins->c64.hp=reader.readC(); + ins->c64.bp=reader.readC(); + ins->c64.lp=reader.readC(); + ins->c64.ch3off=reader.readC(); + + // weird storage + if (volIsCutoff) { + // move to alg (new cutoff) + ins->std.algMacro.len=ins->std.volMacro.len; + ins->std.algMacro.loop=ins->std.volMacro.loop; + ins->std.algMacro.rel=ins->std.volMacro.rel; + for (int j=0; jstd.algMacro.len; j++) { + ins->std.algMacro.val[j]=-(ins->std.volMacro.val[j]-18); + } + ins->std.volMacro.len=0; + memset(ins->std.volMacro.val,0,256*sizeof(int)); + } + for (int j=0; jstd.dutyMacro.len; j++) { + ins->std.dutyMacro.val[j]-=12; + } + } + + if (ds.system[0]==DIV_SYSTEM_GB && ds.version>0x11) { + ins->gb.envVol=reader.readC(); + ins->gb.envDir=reader.readC(); + ins->gb.envLen=reader.readC(); + ins->gb.soundLen=reader.readC(); + ins->std.volMacro.open=false; + + logD("GB data: vol %d dir %d len %d sl %d",ins->gb.envVol,ins->gb.envDir,ins->gb.envLen,ins->gb.soundLen); + } else if (ds.system[0]==DIV_SYSTEM_GB) { + // set software envelope flag + ins->gb.softEnv=true; + // try to convert macro to envelope in case the user decides to switch to them + if (ins->std.volMacro.len>0) { + ins->gb.envVol=ins->std.volMacro.val[0]; + if (ins->std.volMacro.val[0]std.volMacro.val[1]) { + ins->gb.envDir=true; + } + if (ins->std.volMacro.val[ins->std.volMacro.len-1]==0) { + ins->gb.soundLen=ins->std.volMacro.len*2; + } + } + } + } + + ds.ins.push_back(ins); + } + + if (ds.version>0x0b) { + ds.waveLen=(unsigned char)reader.readC(); + logI("reading wavetables (%d)...",ds.waveLen); + if (ds.waveLen>0) ds.wave.reserve(ds.waveLen); + for (int i=0; ilen=(unsigned char)reader.readI(); + if (ds.system[0]==DIV_SYSTEM_GB) { + wave->max=15; + } + if (ds.system[0]==DIV_SYSTEM_NES_FDS) { + wave->max=63; + } + if (wave->len>65) { + logE("invalid wave length %d. are we doing something wrong?",wave->len); + lastError="file is corrupt or unreadable at wavetables"; + delete[] file; + return false; + } + logD("%d length %d",i,wave->len); + for (int j=0; jlen; j++) { + if (ds.version<0x0e) { + wave->data[j]=reader.readC(); + } else { + wave->data[j]=reader.readI(); + } + wave->data[j]&=wave->max; + } + // #FDS4Bit + if (ds.system[0]==DIV_SYSTEM_NES_FDS && ds.version<0x1a) { + for (int j=0; jlen; j++) { + wave->data[j]*=4; + } + } + ds.wave.push_back(wave); + } + + // sometimes there's a single length 0 wavetable in the file. I don't know why. + if (ds.waveLen==1) { + if (ds.wave[0]->len==0) { + ds.clearWavetables(); + } + } + } + + logV("%x",reader.tell()); + + logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen); + for (int i=0; ipat[i]; + if (ds.version<0x0a) { + chan.effectCols=1; + } else { + chan.effectCols=reader.readC(); + } + logD("%d fx rows: %d",i,chan.effectCols); + if (chan.effectCols>4 || chan.effectCols<1) { + logE("invalid effect column count %d. are you sure everything is ok?",chan.effectCols); + lastError="file is corrupt or unreadable at effect columns"; + delete[] file; + return false; + } + for (int j=0; jordersLen; j++) { + DivPattern* pat=chan.getPattern(ds.subsong[0]->orders.ord[i][j],true); + if (ds.version>0x08) { // current pattern format + for (int k=0; kpatLen; k++) { + // note + pat->data[k][0]=reader.readS(); + // octave + pat->data[k][1]=reader.readS(); + if (ds.system[0]==DIV_SYSTEM_SMS && ds.version<0x0e && pat->data[k][1]>0) { + // apparently it was up one octave before + pat->data[k][1]--; + } else if (ds.system[0]==DIV_SYSTEM_GENESIS && ds.version<0x0e && pat->data[k][1]>0 && i>5) { + // ditto + pat->data[k][1]--; + } + if (ds.version<0x12) { + if (ds.system[0]==DIV_SYSTEM_GB && i==3 && pat->data[k][1]>0) { + // back then noise was 2 octaves lower + pat->data[k][1]-=2; + } + } + if (ds.system[0]==DIV_SYSTEM_YMU759 && pat->data[k][0]!=0) { + // apparently YMU759 is stored 2 octaves lower + pat->data[k][1]+=2; + } + if (pat->data[k][0]==0 && pat->data[k][1]!=0) { + logD("what? %d:%d:%d note %d octave %d",i,j,k,pat->data[k][0],pat->data[k][1]); + pat->data[k][0]=12; + pat->data[k][1]--; + } + // volume + pat->data[k][3]=reader.readS(); + if (ds.version<0x0a) { + // back then volume was stored as 00-ff instead of 00-7f/0-f + if (i>5) { + pat->data[k][3]>>=4; + } else { + pat->data[k][3]>>=1; + } + } + if (ds.version<0x12) { + if (ds.system[0]==DIV_SYSTEM_GB && i==2 && pat->data[k][3]>0) { + // volume range of GB wave channel was 0-3 rather than 0-F + pat->data[k][3]=(pat->data[k][3]&3)*5; + } + } + for (int l=0; ldata[k][4+(l<<1)]=reader.readS(); + pat->data[k][5+(l<<1)]=reader.readS(); + + if (ds.version<0x14) { + if (pat->data[k][4+(l<<1)]==0xe5 && pat->data[k][5+(l<<1)]!=-1) { + pat->data[k][5+(l<<1)]=128+((pat->data[k][5+(l<<1)]-128)/4); + } + } + } + // instrument + pat->data[k][2]=reader.readS(); + + // this is sad + if (ds.system[0]==DIV_SYSTEM_NES_FDS) { + if (i==5 && pat->data[k][2]!=-1) { + if (pat->data[k][2]>=0 && pat->data[k][2]data[k][2]]->type=DIV_INS_FDS; + } + } + } + } + } else { // historic pattern format + if (i<16) pat->data[0][2]=historicColIns[i]; + for (int k=0; kpatLen; k++) { + // note + pat->data[k][0]=reader.readC(); + // octave + pat->data[k][1]=reader.readC(); + if (pat->data[k][0]!=0) { + // YMU759 is stored 2 octaves lower + pat->data[k][1]+=2; + } + if (pat->data[k][0]==0 && pat->data[k][1]!=0) { + logD("what? %d:%d:%d note %d octave %d",i,j,k,pat->data[k][0],pat->data[k][1]); + pat->data[k][0]=12; + pat->data[k][1]--; + } + // volume and effect + unsigned char vol=reader.readC(); + unsigned char fx=reader.readC(); + unsigned char fxVal=reader.readC(); + pat->data[k][3]=(vol==0x80 || vol==0xff)?-1:vol; + // effect + pat->data[k][4]=(fx==0x80 || fx==0xff)?-1:fx; + pat->data[k][5]=(fxVal==0x80 || fx==0xff)?-1:fxVal; + // instrument + if (ds.version>0x05) { + pat->data[k][2]=reader.readC(); + if (pat->data[k][2]==0x80 || pat->data[k][2]==0xff) pat->data[k][2]=-1; + } + } + } + } + } + + int ymuSampleRate=20; + + ds.sampleLen=(unsigned char)reader.readC(); + logI("reading samples (%d)...",ds.sampleLen); + if (ds.version<0x0b && ds.sampleLen>0) { + // it appears this byte stored the YMU759 sample rate + ymuSampleRate=reader.readC(); + } + if (ds.sampleLen>0) ds.sample.reserve(ds.sampleLen); + for (int i=0; i0x16) { + sample->name=reader.readString((unsigned char)reader.readC()); + } else { + sample->name=""; + } + logD("%d name %s (%d)",i,sample->name.c_str(),length); + sample->rate=22050; + if (ds.version>=0x0b) { + sample->rate=fileToDivRate(reader.readC()); + sample->centerRate=sample->rate; + pitch=reader.readC(); + vol=reader.readC(); + } + if (ds.version<=0x08) { + sample->rate=ymuSampleRate*400; + } + if (ds.version>0x15) { + sample->depth=(DivSampleDepth)reader.readC(); + if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && sample->depth!=DIV_SAMPLE_DEPTH_16BIT) { + logW("%d: sample depth is wrong! (%d)",i,(int)sample->depth); + sample->depth=DIV_SAMPLE_DEPTH_16BIT; + } + } else { + if (ds.version>0x08) { + sample->depth=DIV_SAMPLE_DEPTH_16BIT; + } else { + // it appears samples were stored as ADPCM back then + sample->depth=DIV_SAMPLE_DEPTH_YMZ_ADPCM; + } + } + if (ds.version>=0x1b) { + // what the hell man... + cutStart=reader.readI(); + cutEnd=reader.readI(); + logV("cutStart: %d cutEnd: %d",cutStart,cutEnd); + } + if (length>0) { + if (ds.version>0x08) { + if (ds.version<0x0b) { + data=new short[1+(length/2)]; + reader.read(data,length); + length/=2; + } else { + data=new short[length]; + reader.read(data,length*2); + } + +#ifdef TA_BIG_ENDIAN + // convert to big-endian + for (int pos=0; pos>8)); + } +#endif + + int scaledLen=ceil((double)length/samplePitches[pitch]); + + if (scaledLen>0) { + // resample + logD("%d: scaling from %d...",i,pitch); + + short* newData=new short[scaledLen]; + memset(newData,0,scaledLen*sizeof(short)); + int k=0; + float mult=(float)(vol)/50.0f; + for (double j=0; j=scaledLen) { + break; + } + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { + float next=(float)(data[(unsigned int)j]-0x80)*mult; + newData[k++]=fmin(fmax(next,-128),127); + } else { + float next=(float)data[(unsigned int)j]*mult; + newData[k++]=fmin(fmax(next,-32768),32767); + } + } + + delete[] data; + data=newData; + } + + logV("length: %d. scaledLen: %d.",length,scaledLen); + + if (ds.version>=0x1b) { + if (cutStart<0 || cutStart>scaledLen) { + logE("cutStart is out of range! (%d, scaledLen: %d)",cutStart,scaledLen); + lastError="file is corrupt or unreadable at samples"; + delete[] file; + return false; + } + if (cutEnd<0 || cutEnd>scaledLen) { + logE("cutEnd is out of range! (%d, scaledLen: %d)",cutEnd,scaledLen); + lastError="file is corrupt or unreadable at samples"; + delete[] file; + return false; + } + if (cutEndinit(scaledLen)) { + logE("%d: error while initializing sample!",i); + } else { + for (int i=0; idepth==DIV_SAMPLE_DEPTH_8BIT) { + sample->data8[i]=data[i]; + } else { + sample->data16[i]=data[i]; + } + } + } + + delete[] data; + } else { + // YMZ ADPCM + adpcmData=new unsigned char[length]; + logV("%x",reader.tell()); + reader.read(adpcmData,length); + for (int i=0; i>4); + } + if (!sample->init(length*2)) { + logE("%d: error while initializing sample!",i); + } + + memcpy(sample->dataZ,adpcmData,length); + delete[] adpcmData; + } + } + ds.sample.push_back(sample); + } + + if (reader.tell()26) version=26; + if (version<24) { + logE("cannot save in this version!"); + lastError="invalid version to save in! this is a bug!"; + return NULL; + } + // check whether system is compound + bool isFlat=false; + if (song.systemLen==2) { + if (song.system[0]==DIV_SYSTEM_YM2612 && song.system[1]==DIV_SYSTEM_SMS) { + isFlat=true; + } + if (song.system[0]==DIV_SYSTEM_YM2612_EXT && song.system[1]==DIV_SYSTEM_SMS) { + isFlat=true; + } + if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { + isFlat=true; + } + if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { + isFlat=true; + } + if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { + isFlat=true; + } + if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { + isFlat=true; + } + } + // fail if more than one system + if (!isFlat && song.systemLen!=1) { + logE("cannot save multiple systems in this format!"); + lastError="multiple systems not possible on .dmf"; + return NULL; + } + // fail if this is an YMU759 song + if (song.system[0]==DIV_SYSTEM_YMU759) { + logE("cannot save YMU759 song!"); + lastError="YMU759 song saving is not supported"; + return NULL; + } + // fail if the system is SMS+OPLL and version<25 + if (version<25 && song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { + logE("Master System FM expansion not supported in 1.0/legacy .dmf!"); + lastError="Master System FM expansion not supported in 1.0/legacy .dmf!"; + return NULL; + } + // fail if the system is NES+VRC7 and version<25 + if (version<25 && song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { + logE("NES + VRC7 not supported in 1.0/legacy .dmf!"); + lastError="NES + VRC7 not supported in 1.0/legacy .dmf!"; + return NULL; + } + // fail if the system is FDS and version<25 + if (version<25 && song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { + logE("FDS not supported in 1.0/legacy .dmf!"); + lastError="FDS not supported in 1.0/legacy .dmf!"; + return NULL; + } + // fail if the system is Furnace-exclusive + if (!isFlat && systemToFileDMF(song.system[0])==0) { + logE("cannot save Furnace-exclusive system song!"); + lastError="this system is not possible on .dmf"; + return NULL; + } + // fail if values are out of range + if (curSubSong->ordersLen>127) { + logE("maximum .dmf song length is 127!"); + lastError="maximum .dmf song length is 127"; + return NULL; + } + if (song.ins.size()>128) { + logE("maximum number of instruments in .dmf is 128!"); + lastError="maximum number of instruments in .dmf is 128"; + return NULL; + } + if (song.wave.size()>64) { + logE("maximum number of wavetables in .dmf is 64!"); + lastError="maximum number of wavetables in .dmf is 64"; + return NULL; + } + for (int i=0; iordersLen; j++) { + if (curOrders->ord[i][j]>0x7f) { + logE("order %d, %d is out of range (0-127)!",curOrders->ord[i][j]); + lastError=fmt::sprintf("order %d, %d is out of range (0-127)",curOrders->ord[i][j]); + return NULL; + } + } + } + saveLock.lock(); + warnings=""; + song.version=version; + song.isDMF=true; + + SafeWriter* w=new SafeWriter; + w->init(); + // write magic + w->write(DIV_DMF_MAGIC,16); + // version + w->writeC(version); + DivSystem sys=DIV_SYSTEM_NULL; + if (song.system[0]==DIV_SYSTEM_YM2612 && song.system[1]==DIV_SYSTEM_SMS) { + w->writeC(systemToFileDMF(DIV_SYSTEM_GENESIS)); + sys=DIV_SYSTEM_GENESIS; + } else if (song.system[0]==DIV_SYSTEM_YM2612_EXT && song.system[1]==DIV_SYSTEM_SMS) { + w->writeC(systemToFileDMF(DIV_SYSTEM_GENESIS_EXT)); + sys=DIV_SYSTEM_GENESIS_EXT; + } else if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { + w->writeC(systemToFileDMF(DIV_SYSTEM_ARCADE)); + sys=DIV_SYSTEM_ARCADE; + } else if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { + w->writeC(systemToFileDMF(DIV_SYSTEM_SMS_OPLL)); + sys=DIV_SYSTEM_SMS_OPLL; + } else if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { + w->writeC(systemToFileDMF(DIV_SYSTEM_NES_VRC7)); + sys=DIV_SYSTEM_NES_VRC7; + } else if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { + w->writeC(systemToFileDMF(DIV_SYSTEM_NES_FDS)); + sys=DIV_SYSTEM_NES_FDS; + } else { + w->writeC(systemToFileDMF(song.system[0])); + sys=song.system[0]; + } + + // song info + w->writeString(song.name,true); + w->writeString(song.author,true); + w->writeC(curSubSong->hilightA); + w->writeC(curSubSong->hilightB); + + int intHz=curSubSong->hz; + + w->writeC(curSubSong->timeBase); + w->writeC(curSubSong->speeds.val[0]); + w->writeC((curSubSong->speeds.len>=2)?curSubSong->speeds.val[1]:curSubSong->speeds.val[0]); + w->writeC((intHz<=53)?0:1); + w->writeC((intHz!=60 && intHz!=50)); + char customHz[4]; + memset(customHz,0,4); + snprintf(customHz,4,"%d",(int)curSubSong->hz); + w->write(customHz,3); + w->writeI(curSubSong->patLen); + w->writeC(curSubSong->ordersLen); + + for (int i=0; iordersLen; j++) { + w->writeC(curOrders->ord[i][j]); + if (version>=25) { + DivPattern* pat=curPat[i].getPattern(j,false); + w->writeString(pat->name,true); + } + } + } + + if (song.subsong.size()>1) { + addWarning("only the currently selected subsong will be saved"); + } + + if (!song.grooves.empty()) { + addWarning("grooves will not be saved"); + } + + if (curSubSong->speeds.len>2) { + addWarning("only the first two speeds will be effective"); + } + + if (curSubSong->virtualTempoD!=curSubSong->virtualTempoN) { + addWarning(".dmf format does not support virtual tempo"); + } + + if (song.tuning<439.99 && song.tuning>440.01) { + addWarning(".dmf format does not support tuning"); + } + + if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { + addWarning("absolute duty/cutoff macro not available in .dmf!"); + addWarning("duty precision will be lost"); + } + + for (DivInstrument* i: song.ins) { + if (i->type==DIV_INS_AMIGA) { + addWarning(".dmf format does not support arbitrary-pitch sample mode"); + break; + } + } + + for (DivInstrument* i: song.ins) { + if (i->type==DIV_INS_FM || i->type==DIV_INS_OPM) { + addWarning("no FM macros in .dmf format"); + break; + } + } + + w->writeC(song.ins.size()); + for (DivInstrument* i: song.ins) { + w->writeString(i->name,true); + + // safety check + if (!isFMSystem(sys) && i->type!=DIV_INS_STD && i->type!=DIV_INS_NES && i->type!=DIV_INS_FDS) { + switch (song.system[0]) { + case DIV_SYSTEM_GB: + i->type=DIV_INS_GB; + break; + case DIV_SYSTEM_NES: + i->type=DIV_INS_NES; + break; + case DIV_SYSTEM_C64_6581: + case DIV_SYSTEM_C64_8580: + i->type=DIV_INS_C64; + break; + case DIV_SYSTEM_PCE: + i->type=DIV_INS_PCE; + break; + case DIV_SYSTEM_YM2610: + case DIV_SYSTEM_YM2610_EXT: + i->type=DIV_INS_AY; + break; + default: + i->type=DIV_INS_STD; + break; + } + } + if (!isSTDSystem(sys) && i->type!=DIV_INS_FM && i->type!=DIV_INS_OPM) { + if (sys==DIV_SYSTEM_ARCADE) { + i->type=DIV_INS_OPM; + } else { + i->type=DIV_INS_FM; + } + } + + w->writeC((i->type==DIV_INS_FM || i->type==DIV_INS_OPM || i->type==DIV_INS_OPLL)?1:0); + if (i->type==DIV_INS_FM || i->type==DIV_INS_OPM || i->type==DIV_INS_OPLL) { // FM + w->writeC(i->fm.alg); + w->writeC(i->fm.fb); + w->writeC(i->fm.fms); + w->writeC(i->fm.ams); + + for (int j=0; j<4; j++) { + DivInstrumentFM::Operator& op=i->fm.op[j]; + w->writeC(op.am); + w->writeC(op.ar); + w->writeC(op.dr); + w->writeC(op.mult); + w->writeC(op.rr); + w->writeC(op.sl); + w->writeC(op.tl); + if ((sys==DIV_SYSTEM_SMS_OPLL || sys==DIV_SYSTEM_NES_VRC7) && j==0) { + w->writeC(i->fm.opllPreset); + } else { + w->writeC(op.dt2); + } + if (sys==DIV_SYSTEM_SMS_OPLL || sys==DIV_SYSTEM_NES_VRC7) { + w->writeC(op.ksr); + w->writeC(op.vib); + w->writeC(op.ksl); + w->writeC(op.ssgEnv); + } else { + w->writeC(op.rs); + w->writeC(op.dt); + w->writeC(op.d2r); + w->writeC(op.ssgEnv); + } + } + } else { // STD + bool volIsCutoff=false; + + if (sys!=DIV_SYSTEM_GB) { + int realVolMacroLen=i->std.volMacro.len; + if (realVolMacroLen>127) realVolMacroLen=127; + if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { + if (i->std.algMacro.len>0) volIsCutoff=true; + if (volIsCutoff) { + if (i->std.volMacro.len>0) { + addWarning(".dmf only supports volume or cutoff macro in C64, but not both. volume macro will be lost."); + } + realVolMacroLen=i->std.algMacro.len; + if (realVolMacroLen>127) realVolMacroLen=127; + w->writeC(realVolMacroLen); + for (int j=0; jwriteI((-i->std.algMacro.val[j])+18); + } + } else { + w->writeC(realVolMacroLen); + for (int j=0; jwriteI(i->std.volMacro.val[j]); + } + } + } else { + w->writeC(realVolMacroLen); + for (int j=0; jwriteI(i->std.volMacro.val[j]); + } + } + if (realVolMacroLen>0) { + if (volIsCutoff) { + w->writeC(i->std.algMacro.loop); + } else { + w->writeC(i->std.volMacro.loop); + } + } + } + + bool arpMacroMode=false; + int arpMacroHowManyFixed=0; + int realArpMacroLen=i->std.arpMacro.len; + for (int j=0; jstd.arpMacro.len; j++) { + if ((i->std.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) { + arpMacroHowManyFixed++; + } + } + if (arpMacroHowManyFixed>=i->std.arpMacro.len-1) { + arpMacroMode=true; + } + if (i->std.arpMacro.len>0) { + if (arpMacroMode && i->std.arpMacro.val[i->std.arpMacro.len-1]==0 && i->std.arpMacro.loop>=i->std.arpMacro.len) { + realArpMacroLen--; + } + } + + if (realArpMacroLen>127) realArpMacroLen=127; + + w->writeC(realArpMacroLen); + + if (arpMacroMode) { + for (int j=0; jstd.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) { + w->writeI(i->std.arpMacro.val[j]^0x40000000); + } else { + w->writeI(i->std.arpMacro.val[j]); + } + } + } else { + for (int j=0; jstd.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) { + w->writeI((i->std.arpMacro.val[j]^0x40000000)+12); + } else { + w->writeI(i->std.arpMacro.val[j]+12); + } + } + } + if (realArpMacroLen>0) { + w->writeC(i->std.arpMacro.loop); + } + w->writeC(arpMacroMode); + + int realDutyMacroLen=i->std.dutyMacro.len; + if (realDutyMacroLen>127) realDutyMacroLen=127; + w->writeC(realDutyMacroLen); + if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { + for (int j=0; jwriteI(i->std.dutyMacro.val[j]+12); + } + } else { + for (int j=0; jwriteI(i->std.dutyMacro.val[j]); + } + } + if (realDutyMacroLen>0) { + w->writeC(i->std.dutyMacro.loop); + } + + int realWaveMacroLen=i->std.waveMacro.len; + if (realWaveMacroLen>127) realWaveMacroLen=127; + w->writeC(realWaveMacroLen); + for (int j=0; jwriteI(i->std.waveMacro.val[j]); + } + if (realWaveMacroLen>0) { + w->writeC(i->std.waveMacro.loop); + } + + if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { + w->writeC(i->c64.triOn); + w->writeC(i->c64.sawOn); + w->writeC(i->c64.pulseOn); + w->writeC(i->c64.noiseOn); + + w->writeC(i->c64.a); + w->writeC(i->c64.d); + w->writeC(i->c64.s); + w->writeC(i->c64.r); + + logW("duty and cutoff precision will be lost!"); + w->writeC((i->c64.duty*100)/4095); + + w->writeC(i->c64.ringMod); + w->writeC(i->c64.oscSync); + + w->writeC(i->c64.toFilter); + w->writeC(volIsCutoff); + w->writeC(i->c64.initFilter); + + w->writeC(i->c64.res); + w->writeC((i->c64.cut*100)/2047); + w->writeC(i->c64.hp); + w->writeC(i->c64.bp); + w->writeC(i->c64.lp); + w->writeC(i->c64.ch3off); + } + + if (sys==DIV_SYSTEM_GB) { + w->writeC(i->gb.envVol); + w->writeC(i->gb.envDir); + w->writeC(i->gb.envLen); + w->writeC(i->gb.soundLen); + } + } + } + + w->writeC(song.wave.size()); + for (DivWavetable* i: song.wave) { + w->writeI(i->len); + if (sys==DIV_SYSTEM_NES_FDS && version<26) { + for (int j=0; jlen; j++) { + w->writeI(i->data[j]>>2); + } + } else { + for (int j=0; jlen; j++) { + w->writeI(i->data[j]); + } + } + } + + bool relWarning=false; + + for (int i=0; iwriteC(curPat[i].effectCols); + + for (int j=0; jordersLen; j++) { + DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false); + for (int k=0; kpatLen; k++) { + if ((pat->data[k][0]==101 || pat->data[k][0]==102) && pat->data[k][1]==0) { + w->writeS(100); + w->writeS(0); + if (!relWarning) { + relWarning=true; + addWarning("note/macro release will be converted to note off!"); + } + } else { + w->writeS(pat->data[k][0]); // note + w->writeS(pat->data[k][1]); // octave + } + w->writeS(pat->data[k][3]); // volume +#ifdef TA_BIG_ENDIAN + for (int l=0; lwriteS(pat->data[k][4+l]); + } +#else + w->write(&pat->data[k][4],2*curPat[i].effectCols*2); // effects +#endif + w->writeS(pat->data[k][2]); // instrument + } + } + } + + if (song.sample.size()>0) { + addWarning("samples' rates will be rounded to nearest compatible value"); + } + + w->writeC(song.sample.size()); + for (DivSample* i: song.sample) { + w->writeI(i->samples); + w->writeString(i->name,true); + w->writeC(divToFileRate(i->rate)); + w->writeC(5); + w->writeC(50); + // i'm too lazy to deal with .dmf's weird way of storing 8-bit samples + w->writeC(16); + // well I can't be lazy if it's on a big-endian system +#ifdef TA_BIG_ENDIAN + for (unsigned int j=0; jlength16; j++) { + w->writeC(((unsigned short)i->data16[j])&0xff); + w->writeC(((unsigned short)i->data16[j])>>8); + } +#else + w->write(i->data16,i->length16); +#endif + } + + saveLock.unlock(); + return w; +} + +static const char* trueFalse[2]={ + "no", "yes" +}; + +static const char* gbEnvDir[2]={ + "down", "up" +}; + +static const char* notes[12]={ + "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" +}; + +static const char* notesNegative[12]={ + "c_", "c+", "d_", "d+", "e_", "f_", "f+", "g_", "g+", "a_", "a+", "b_" +}; + +static const char* sampleLoopModes[4]={ + "forward", "backward", "ping-pong", "invalid" +}; + +void writeTextMacro(SafeWriter* w, DivInstrumentMacro& m, const char* name, bool& wroteMacroHeader) { + if ((m.open&6)==0 && m.len<1) return; + if (!wroteMacroHeader) { + w->writeText("- macros:\n"); + wroteMacroHeader=true; + } + w->writeText(fmt::sprintf(" - %s:",name)); + int len=m.len; + switch (m.open&6) { + case 2: + len=16; + w->writeText(" [ADSR]"); + break; + case 4: + len=16; + w->writeText(" [LFO]"); + break; + } + if (m.mode) { + w->writeText(fmt::sprintf(" [MODE %d]",m.mode)); + } + if (m.delay>0) { + w->writeText(fmt::sprintf(" [DELAY %d]",m.delay)); + } + if (m.speed>1) { + w->writeText(fmt::sprintf(" [SPEED %d]",m.speed)); + } + for (int i=0; iwriteText(" |"); + } + if (i==m.rel) { + w->writeText(" /"); + } + w->writeText(fmt::sprintf(" %d",m.val[i])); + } + w->writeText("\n"); +} + +SafeWriter* DivEngine::saveText(bool separatePatterns) { + saveLock.lock(); + + SafeWriter* w=new SafeWriter; + w->init(); + + w->writeText(fmt::sprintf("# Furnace Text Export\n\ngenerated by Furnace %s (%d)\n\n# Song Information\n\n",DIV_VERSION,DIV_ENGINE_VERSION)); + w->writeText(fmt::sprintf("- name: %s\n",song.name)); + w->writeText(fmt::sprintf("- author: %s\n",song.author)); + w->writeText(fmt::sprintf("- album: %s\n",song.category)); + w->writeText(fmt::sprintf("- system: %s\n",song.systemName)); + w->writeText(fmt::sprintf("- tuning: %g\n\n",song.tuning)); + + w->writeText(fmt::sprintf("- instruments: %d\n",song.insLen)); + w->writeText(fmt::sprintf("- wavetables: %d\n",song.waveLen)); + w->writeText(fmt::sprintf("- samples: %d\n\n",song.sampleLen)); + + w->writeText("# Sound Chips\n\n"); + + for (int i=0; iwriteText(fmt::sprintf("- %s\n",getSystemName(song.system[i]))); + w->writeText(fmt::sprintf(" - id: %.2X\n",(int)song.system[i])); + w->writeText(fmt::sprintf(" - volume: %g\n",song.systemVol[i])); + w->writeText(fmt::sprintf(" - panning: %g\n",song.systemPan[i])); + w->writeText(fmt::sprintf(" - front/rear: %g\n",song.systemPanFR[i])); + + String sysFlags=song.systemFlags[i].toString(); + + if (!sysFlags.empty()) { + w->writeText(fmt::sprintf(" - flags:\n```\n%s\n```\n",sysFlags)); + } + } + + if (!song.notes.empty()) { + w->writeText("\n# Song Comments\n\n"); + w->writeText(song.notes); + } + + w->writeText("\n# Instruments\n\n"); + + for (int i=0; iwriteText(fmt::sprintf("## %.2X: %s\n\n",i,ins->name)); + + w->writeText(fmt::sprintf("- type: %d\n",(int)ins->type)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM || ins->type==DIV_INS_ESFM) { + w->writeText("- FM parameters:\n"); + w->writeText(fmt::sprintf(" - ALG: %d\n",ins->fm.alg)); + w->writeText(fmt::sprintf(" - FB: %d\n",ins->fm.fb)); + w->writeText(fmt::sprintf(" - FMS: %d\n",ins->fm.fms)); + w->writeText(fmt::sprintf(" - AMS: %d\n",ins->fm.ams)); + w->writeText(fmt::sprintf(" - FMS2: %d\n",ins->fm.fms2)); + w->writeText(fmt::sprintf(" - AMS2: %d\n",ins->fm.ams2)); + w->writeText(fmt::sprintf(" - operators: %d\n",ins->fm.ops)); + w->writeText(fmt::sprintf(" - OPLL patch: %d\n",ins->fm.opllPreset)); + w->writeText(fmt::sprintf(" - fixed drum freq: %s\n",trueFalse[ins->fm.fixedDrums?1:0])); + w->writeText(fmt::sprintf(" - kick freq: %.4X\n",ins->fm.kickFreq)); + w->writeText(fmt::sprintf(" - snare/hat freq: %.4X\n",ins->fm.snareHatFreq)); + w->writeText(fmt::sprintf(" - tom/top freq: %.4X\n",ins->fm.tomTopFreq)); + + for (int j=0; jfm.ops; j++) { + DivInstrumentFM::Operator& op=ins->fm.op[j]; + + w->writeText(fmt::sprintf(" - operator %d:\n",j)); + w->writeText(fmt::sprintf(" - enabled: %s\n",trueFalse[op.enable?1:0])); + w->writeText(fmt::sprintf(" - AM: %d\n",op.am)); + w->writeText(fmt::sprintf(" - AR: %d\n",op.ar)); + w->writeText(fmt::sprintf(" - DR: %d\n",op.dr)); + w->writeText(fmt::sprintf(" - MULT: %d\n",op.mult)); + w->writeText(fmt::sprintf(" - RR: %d\n",op.rr)); + w->writeText(fmt::sprintf(" - SL: %d\n",op.sl)); + w->writeText(fmt::sprintf(" - TL: %d\n",op.tl)); + w->writeText(fmt::sprintf(" - DT2: %d\n",op.dt2)); + w->writeText(fmt::sprintf(" - RS: %d\n",op.rs)); + w->writeText(fmt::sprintf(" - DT: %d\n",op.dt)); + w->writeText(fmt::sprintf(" - D2R: %d\n",op.d2r)); + w->writeText(fmt::sprintf(" - SSG-EG: %d\n",op.ssgEnv)); + w->writeText(fmt::sprintf(" - DAM: %d\n",op.dam)); + w->writeText(fmt::sprintf(" - DVB: %d\n",op.dvb)); + w->writeText(fmt::sprintf(" - EGT: %d\n",op.egt)); + w->writeText(fmt::sprintf(" - KSL: %d\n",op.ksl)); + w->writeText(fmt::sprintf(" - SUS: %d\n",op.sus)); + w->writeText(fmt::sprintf(" - VIB: %d\n",op.vib)); + w->writeText(fmt::sprintf(" - WS: %d\n",op.ws)); + w->writeText(fmt::sprintf(" - KSR: %d\n",op.ksr)); + w->writeText(fmt::sprintf(" - TL volume scale: %d\n",op.kvs)); + } + } + + if (ins->type==DIV_INS_ESFM) { + w->writeText("- ESFM parameters:\n"); + w->writeText(fmt::sprintf(" - noise mode: %d\n",ins->esfm.noise)); + + for (int j=0; jfm.ops; j++) { + DivInstrumentESFM::Operator& opE=ins->esfm.op[j]; + + w->writeText(fmt::sprintf(" - operator %d:\n",j)); + w->writeText(fmt::sprintf(" - DL: %d\n",opE.delay)); + w->writeText(fmt::sprintf(" - OL: %d\n",opE.outLvl)); + w->writeText(fmt::sprintf(" - MI: %d\n",opE.modIn)); + w->writeText(fmt::sprintf(" - output left: %s\n",trueFalse[opE.left?1:0])); + w->writeText(fmt::sprintf(" - output right: %s\n",trueFalse[opE.right?1:0])); + w->writeText(fmt::sprintf(" - CT: %d\n",opE.ct)); + w->writeText(fmt::sprintf(" - DT: %d\n",opE.dt)); + w->writeText(fmt::sprintf(" - fixed frequency: %s\n",trueFalse[opE.fixed?1:0])); + } + } + + if (ins->type==DIV_INS_GB) { + w->writeText("- Game Boy parameters:\n"); + w->writeText(fmt::sprintf(" - volume: %d\n",ins->gb.envVol)); + w->writeText(fmt::sprintf(" - direction: %s\n",gbEnvDir[ins->gb.envDir?1:0])); + w->writeText(fmt::sprintf(" - length: %d\n",ins->gb.envLen)); + w->writeText(fmt::sprintf(" - sound length: %d\n",ins->gb.soundLen)); + w->writeText(fmt::sprintf(" - use software envelope: %s\n",trueFalse[ins->gb.softEnv?1:0])); + w->writeText(fmt::sprintf(" - always initialize: %s\n",trueFalse[ins->gb.softEnv?1:0])); + if (ins->gb.hwSeqLen>0) { + w->writeText(" - hardware sequence:\n"); + for (int j=0; jgb.hwSeqLen; j++) { + w->writeText(fmt::sprintf(" - %d: %.2X %.4X\n",j,ins->gb.hwSeq[j].cmd,ins->gb.hwSeq[j].data)); + } + } + } + + bool header=false; + writeTextMacro(w,ins->std.volMacro,"vol",header); + writeTextMacro(w,ins->std.arpMacro,"arp",header); + writeTextMacro(w,ins->std.dutyMacro,"duty",header); + writeTextMacro(w,ins->std.waveMacro,"wave",header); + writeTextMacro(w,ins->std.pitchMacro,"pitch",header); + writeTextMacro(w,ins->std.panLMacro,"panL",header); + writeTextMacro(w,ins->std.panRMacro,"panR",header); + writeTextMacro(w,ins->std.phaseResetMacro,"phaseReset",header); + writeTextMacro(w,ins->std.ex1Macro,"ex1",header); + writeTextMacro(w,ins->std.ex2Macro,"ex2",header); + writeTextMacro(w,ins->std.ex3Macro,"ex3",header); + writeTextMacro(w,ins->std.ex4Macro,"ex4",header); + writeTextMacro(w,ins->std.ex5Macro,"ex5",header); + writeTextMacro(w,ins->std.ex6Macro,"ex6",header); + writeTextMacro(w,ins->std.ex7Macro,"ex7",header); + writeTextMacro(w,ins->std.ex8Macro,"ex8",header); + writeTextMacro(w,ins->std.algMacro,"alg",header); + writeTextMacro(w,ins->std.fbMacro,"fb",header); + writeTextMacro(w,ins->std.fmsMacro,"fms",header); + writeTextMacro(w,ins->std.amsMacro,"ams",header); + + // TODO: the rest + w->writeText("\n"); + } + + w->writeText("\n# Wavetables\n\n"); + + for (int i=0; iwriteText(fmt::sprintf("- %d (%dx%d):",i,wave->len+1,wave->max+1)); + for (int j=0; j<=wave->len; j++) { + w->writeText(fmt::sprintf(" %d",wave->data[j])); + } + w->writeText("\n"); + } + + w->writeText("\n# Samples\n\n"); + + for (int i=0; iwriteText(fmt::sprintf("## %.2X: %s\n\n",i,sample->name)); + + w->writeText(fmt::sprintf("- format: %d\n",(int)sample->depth)); + w->writeText(fmt::sprintf("- data length: %d\n",sample->getCurBufLen())); + w->writeText(fmt::sprintf("- samples: %d\n",sample->samples)); + w->writeText(fmt::sprintf("- rate: %d\n",sample->centerRate)); + w->writeText(fmt::sprintf("- compat rate: %d\n",sample->rate)); + w->writeText(fmt::sprintf("- loop: %s\n",trueFalse[sample->loop?1:0])); + if (sample->loop) { + w->writeText(fmt::sprintf(" - start: %d\n",sample->loopStart)); + w->writeText(fmt::sprintf(" - end: %d\n",sample->loopEnd)); + w->writeText(fmt::sprintf(" - mode: %s\n",sampleLoopModes[sample->loopMode&3])); + } + w->writeText(fmt::sprintf("- BRR emphasis: %s\n",trueFalse[sample->brrEmphasis?1:0])); + w->writeText(fmt::sprintf("- dither: %s\n",trueFalse[sample->dither?1:0])); + + // TODO' render matrix + unsigned char* buf=(unsigned char*)sample->getCurBuf(); + unsigned int bufLen=sample->getCurBufLen(); + w->writeText("\n```"); + for (unsigned int i=0; iwriteText(fmt::sprintf("\n%.8X:",i)); + w->writeText(fmt::sprintf(" %.2X",buf[i])); + } + w->writeText("\n```\n\n"); + } + + w->writeText("\n# Subsongs\n\n"); + + for (size_t i=0; iwriteText(fmt::sprintf("## %d: %s\n\n",(int)i,s->name)); + + w->writeText(fmt::sprintf("- tick rate: %g\n",s->hz)); + w->writeText(fmt::sprintf("- speeds:")); + for (int j=0; jspeeds.len; j++) { + w->writeText(fmt::sprintf(" %d",s->speeds.val[j])); + } + w->writeText("\n"); + w->writeText(fmt::sprintf("- virtual tempo: %d/%d\n",s->virtualTempoN,s->virtualTempoD)); + w->writeText(fmt::sprintf("- time base: %d\n",s->timeBase)); + w->writeText(fmt::sprintf("- pattern length: %d\n",s->patLen)); + w->writeText(fmt::sprintf("\norders:\n```\n")); + + for (int j=0; jordersLen; j++) { + w->writeText(fmt::sprintf("%.2X |",j)); + for (int k=0; kwriteText(fmt::sprintf(" %.2X",s->orders.ord[k][j])); + } + w->writeText("\n"); + } + w->writeText("```\n\n## Patterns\n\n"); + + if (separatePatterns) { + w->writeText("TODO: separate patterns\n\n"); + } else { + for (int j=0; jordersLen; j++) { + w->writeText(fmt::sprintf("----- ORDER %.2X\n",j)); + + for (int k=0; kpatLen; k++) { + w->writeText(fmt::sprintf("%.2X ",k)); + + for (int l=0; lpat[l].getPattern(s->orders.ord[l][j],false); + + int note=p->data[k][0]; + int octave=p->data[k][1]; + + if (note==0 && octave==0) { + w->writeText("|... "); + } else if (note==100) { + w->writeText("|OFF "); + } else if (note==101) { + w->writeText("|=== "); + } else if (note==102) { + w->writeText("|REL "); + } else if ((octave>9 && octave<250) || note>12) { + w->writeText("|??? "); + } else { + if (octave>=128) octave-=256; + if (note>11) { + note-=12; + octave++; + } + w->writeText(fmt::sprintf("|%s%d ",(octave<0)?notesNegative[note]:notes[note],(octave<0)?(-octave):octave)); + } + + if (p->data[k][2]==-1) { + w->writeText(".. "); + } else { + w->writeText(fmt::sprintf("%.2X ",p->data[k][2]&0xff)); + } + + if (p->data[k][3]==-1) { + w->writeText(".."); + } else { + w->writeText(fmt::sprintf("%.2X",p->data[k][3]&0xff)); + } + + for (int m=0; mpat[l].effectCols; m++) { + if (p->data[k][4+(m<<1)]==-1) { + w->writeText(" .."); + } else { + w->writeText(fmt::sprintf(" %.2X",p->data[k][4+(m<<1)]&0xff)); + } + if (p->data[k][5+(m<<1)]==-1) { + w->writeText(".."); + } else { + w->writeText(fmt::sprintf("%.2X",p->data[k][5+(m<<1)]&0xff)); + } + } + } + + w->writeText("\n"); + } + } + } + + } + + saveLock.unlock(); + return w; +} diff --git a/src/engine/fileOps/fc.cpp b/src/engine/fileOps/fc.cpp new file mode 100644 index 000000000..7c8869fc9 --- /dev/null +++ b/src/engine/fileOps/fc.cpp @@ -0,0 +1,708 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "fileOpsCommon.h" + +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::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); + seq.reserve(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..."); + ds.wave.reserve(80); + 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 + ds.wave.reserve(48); + 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; + ds.ins.reserve(64 - 5); + 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; +} + diff --git a/src/engine/fileOps/fileOpsCommon.cpp b/src/engine/fileOps/fileOpsCommon.cpp new file mode 100644 index 000000000..7362171cb --- /dev/null +++ b/src/engine/fileOps/fileOpsCommon.cpp @@ -0,0 +1,149 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "fileOpsCommon.h" + +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; +} diff --git a/src/engine/fileOps/fileOpsCommon.h b/src/engine/fileOps/fileOpsCommon.h new file mode 100644 index 000000000..baceadaf3 --- /dev/null +++ b/src/engine/fileOps/fileOpsCommon.h @@ -0,0 +1,54 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../dataErrors.h" +#include "../engine.h" +#include "../../ta-log.h" +#include +#include + +#define DIV_READ_SIZE 131072 + +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) {} +}; + +#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" diff --git a/src/engine/fileOps/ftm.cpp b/src/engine/fileOps/ftm.cpp new file mode 100644 index 000000000..152c7c6d3 --- /dev/null +++ b/src/engine/fileOps/ftm.cpp @@ -0,0 +1,611 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "fileOpsCommon.h" + +#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); + ds.subsong.reserve(totalSongs); + for (int i=0; i<=totalSongs; i++) { + String subSongName=reader.readString(); + ds.subsong.push_back(new DivSubSong); + ds.subsong[i]->name=subSongName; + ds.subsong[i]->hilightA=hilightA; + ds.subsong[i]->hilightB=hilightB; + if (customHz!=0) { + ds.subsong[i]->hz=customHz; + } + 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; +} + diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp new file mode 100644 index 000000000..82054dd6c --- /dev/null +++ b/src/engine/fileOps/fur.cpp @@ -0,0 +1,2710 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "fileOpsCommon.h" + +short newFormatNotes[180]={ + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -5 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -4 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -3 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -2 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // -1 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 0 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 1 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 2 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 3 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 4 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 5 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 6 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 7 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, // 8 + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 // 9 +}; + +short newFormatOctaves[180]={ + 250, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, 251, // -5 + 251, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, // -4 + 252, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, 253, // -3 + 253, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, // -2 + 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // -1 + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1 + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 2 + 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 3 + 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 4 + 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 5 + 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // 6 + 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 7 + 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 8 + 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 9 +}; + +struct PatToWrite { + unsigned short subsong, chan, pat; + PatToWrite(unsigned short s, unsigned short c, unsigned short p): + subsong(s), + chan(c), + pat(p) {} +}; + +void DivEngine::putAssetDirData(SafeWriter* w, std::vector& dir) { + size_t blockStartSeek, blockEndSeek; + + w->write("ADIR",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeI(dir.size()); + + for (DivAssetDir& i: dir) { + w->writeString(i.name,false); + w->writeS(i.entries.size()); + for (int j: i.entries) { + w->writeC(j); + } + } + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); +} + +DivDataErrors DivEngine::readAssetDirData(SafeReader& reader, std::vector& dir) { + char magic[4]; + reader.read(magic,4); + if (memcmp(magic,"ADIR",4)!=0) { + logV("header is invalid: %c%c%c%c",magic[0],magic[1],magic[2],magic[3]); + return DIV_DATA_INVALID_HEADER; + } + reader.readI(); // reserved + + unsigned int numDirs=reader.readI(); + + dir.reserve(numDirs); + for (unsigned int i=0; i>4)&3) { + case 0: + newFlags.set("chipType",0); + break; + case 1: + newFlags.set("chipType",1); + break; + case 2: + newFlags.set("chipType",2); + break; + case 3: + newFlags.set("chipType",3); + break; + } + if (oldFlags&64) newFlags.set("stereo",true); + if (oldFlags&128) newFlags.set("halfClock",true); + newFlags.set("stereoSep",(int)((oldFlags>>8)&255)); + break; + case DIV_SYSTEM_AMIGA: + if (oldFlags&1) newFlags.set("clockSel",1); + if (oldFlags&2) newFlags.set("chipType",1); + if (oldFlags&4) newFlags.set("bypassLimits",true); + newFlags.set("stereoSep",(int)((oldFlags>>8)&127)); + break; + case DIV_SYSTEM_YM2151: + switch (oldFlags&255) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + } + break; + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: + case DIV_SYSTEM_YM2612_DUALPCM: + case DIV_SYSTEM_YM2612_DUALPCM_EXT: + switch (oldFlags&0x7fffffff) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + case 3: + newFlags.set("clockSel",3); + break; + case 4: + newFlags.set("clockSel",4); + break; + } + if (oldFlags&0x80000000) newFlags.set("ladderEffect",true); + break; + case DIV_SYSTEM_TIA: + newFlags.set("clockSel",(int)(oldFlags&1)); + switch ((oldFlags>>1)&3) { + case 0: + newFlags.set("mixingType",0); + break; + case 1: + newFlags.set("mixingType",1); + break; + case 2: + newFlags.set("mixingType",2); + break; + } + break; + case DIV_SYSTEM_VIC20: + newFlags.set("clockSel",(int)(oldFlags&1)); + break; + case DIV_SYSTEM_SNES: + newFlags.set("volScaleL",(int)(oldFlags&127)); + newFlags.set("volScaleR",(int)((oldFlags>>8)&127)); + break; + case DIV_SYSTEM_OPLL: + case DIV_SYSTEM_OPLL_DRUMS: + switch (oldFlags&15) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + case 3: + newFlags.set("clockSel",3); + break; + } + switch (oldFlags>>4) { + case 0: + newFlags.set("patchSet",0); + break; + case 1: + newFlags.set("patchSet",1); + break; + case 2: + newFlags.set("patchSet",2); + break; + case 3: + newFlags.set("patchSet",3); + break; + } + break; + case DIV_SYSTEM_N163: + switch (oldFlags&15) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + } + newFlags.set("channels",(int)((oldFlags>>4)&7)); + if (oldFlags&128) newFlags.set("multiplex",true); + break; + case DIV_SYSTEM_YM2203: + case DIV_SYSTEM_YM2203_EXT: + switch (oldFlags&31) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + case 3: + newFlags.set("clockSel",3); + break; + case 4: + newFlags.set("clockSel",4); + break; + case 5: + newFlags.set("clockSel",5); + break; + } + switch ((oldFlags>>5)&3) { + case 0: + newFlags.set("prescale",0); + break; + case 1: + newFlags.set("prescale",1); + break; + case 2: + newFlags.set("prescale",2); + break; + } + break; + case DIV_SYSTEM_YM2608: + case DIV_SYSTEM_YM2608_EXT: + switch (oldFlags&31) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + } + switch ((oldFlags>>5)&3) { + case 0: + newFlags.set("prescale",0); + break; + case 1: + newFlags.set("prescale",1); + break; + case 2: + newFlags.set("prescale",2); + break; + } + break; + case DIV_SYSTEM_OPL: + case DIV_SYSTEM_OPL2: + case DIV_SYSTEM_Y8950: + case DIV_SYSTEM_OPL_DRUMS: + case DIV_SYSTEM_OPL2_DRUMS: + case DIV_SYSTEM_Y8950_DRUMS: + case DIV_SYSTEM_YMZ280B: + switch (oldFlags&0xff) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + case 3: + newFlags.set("clockSel",3); + break; + case 4: + newFlags.set("clockSel",4); + break; + case 5: + newFlags.set("clockSel",5); + break; + } + break; + case DIV_SYSTEM_OPL3: + case DIV_SYSTEM_OPL3_DRUMS: + switch (oldFlags&0xff) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + case 3: + newFlags.set("clockSel",3); + break; + case 4: + newFlags.set("clockSel",4); + break; + } + break; + case DIV_SYSTEM_PCSPKR: + newFlags.set("speakerType",(int)(oldFlags&3)); + break; + case DIV_SYSTEM_RF5C68: + switch (oldFlags&15) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + } + switch (oldFlags>>4) { + case 0: + newFlags.set("chipType",0); + break; + case 1: + newFlags.set("chipType",1); + break; + } + break; + case DIV_SYSTEM_VRC7: + switch (oldFlags&15) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + case 3: + newFlags.set("clockSel",3); + break; + } + break; + case DIV_SYSTEM_SFX_BEEPER: + case DIV_SYSTEM_SFX_BEEPER_QUADTONE: + newFlags.set("clockSel",(int)(oldFlags&1)); + break; + case DIV_SYSTEM_SCC: + case DIV_SYSTEM_SCC_PLUS: + switch (oldFlags&63) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + case 3: + newFlags.set("clockSel",3); + break; + } + break; + case DIV_SYSTEM_MSM6295: + switch (oldFlags&63) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + case 3: + newFlags.set("clockSel",3); + break; + case 4: + newFlags.set("clockSel",4); + break; + case 5: + newFlags.set("clockSel",5); + break; + case 6: + newFlags.set("clockSel",6); + break; + case 7: + newFlags.set("clockSel",7); + break; + case 8: + newFlags.set("clockSel",8); + break; + case 9: + newFlags.set("clockSel",9); + break; + case 10: + newFlags.set("clockSel",10); + break; + case 11: + newFlags.set("clockSel",11); + break; + case 12: + newFlags.set("clockSel",12); + break; + case 13: + newFlags.set("clockSel",13); + break; + case 14: + newFlags.set("clockSel",14); + break; + } + if (oldFlags&128) newFlags.set("rateSel",true); + break; + case DIV_SYSTEM_MSM6258: + switch (oldFlags) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + case 3: + newFlags.set("clockSel",3); + break; + } + break; + case DIV_SYSTEM_OPL4: + case DIV_SYSTEM_OPL4_DRUMS: + switch (oldFlags&0xff) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + case 2: + newFlags.set("clockSel",2); + break; + } + break; + case DIV_SYSTEM_X1_010: + switch (oldFlags&15) { + case 0: + newFlags.set("clockSel",0); + break; + case 1: + newFlags.set("clockSel",1); + break; + } + if (oldFlags&16) newFlags.set("stereo",true); + break; + case DIV_SYSTEM_SOUND_UNIT: + newFlags.set("clockSel",(int)(oldFlags&1)); + if (oldFlags&4) newFlags.set("echo",true); + if (oldFlags&8) newFlags.set("swapEcho",true); + newFlags.set("sampleMemSize",(int)((oldFlags>>4)&1)); + if (oldFlags&32) newFlags.set("pdm",true); + newFlags.set("echoDelay",(int)((oldFlags>>8)&63)); + newFlags.set("echoFeedback",(int)((oldFlags>>16)&15)); + newFlags.set("echoResolution",(int)((oldFlags>>20)&15)); + newFlags.set("echoVol",(int)((oldFlags>>24)&255)); + break; + case DIV_SYSTEM_PCM_DAC: + if (!oldFlags) oldFlags=0x1f0000|44099; + newFlags.set("rate",(int)((oldFlags&0xffff)+1)); + newFlags.set("outDepth",(int)((oldFlags>>16)&15)); + if (oldFlags&0x100000) newFlags.set("stereo",true); + break; + case DIV_SYSTEM_QSOUND: + newFlags.set("echoDelay",(int)(oldFlags&0xfff)); + newFlags.set("echoFeedback",(int)((oldFlags>>12)&255)); + break; + default: + break; + } +} + +bool DivEngine::loadFur(unsigned char* file, size_t len) { + unsigned int insPtr[256]; + unsigned int wavePtr[256]; + unsigned int samplePtr[256]; + unsigned int subSongPtr[256]; + unsigned int sysFlagsPtr[DIV_MAX_CHIPS]; + unsigned int assetDirPtr[3]; + std::vector patPtr; + int numberOfSubSongs=0; + char magic[5]; + memset(magic,0,5); + SafeReader reader=SafeReader(file,len); + warnings=""; + assetDirPtr[0]=0; + assetDirPtr[1]=0; + assetDirPtr[2]=0; + try { + DivSong ds; + DivSubSong* subSong=ds.subsong[0]; + + if (!reader.seek(16,SEEK_SET)) { + logE("premature end of file!"); + lastError="incomplete file"; + delete[] file; + return false; + } + ds.version=reader.readS(); + logI("module version %d (0x%.2x)",ds.version,ds.version); + + if (ds.version>DIV_ENGINE_VERSION) { + logW("this module was created with a more recent version of Furnace!"); + addWarning("this module was created with a more recent version of Furnace!"); + } + + if (ds.version<37) { // compat flags not stored back then + ds.limitSlides=true; + ds.linearPitch=1; + ds.loopModality=0; + } + if (ds.version<43) { + ds.properNoiseLayout=false; + ds.waveDutyIsVol=false; + } + if (ds.version<45) { + ds.resetMacroOnPorta=true; + ds.legacyVolumeSlides=true; + ds.compatibleArpeggio=true; + ds.noteOffResetsSlides=true; + ds.targetResetsSlides=true; + } + if (ds.version<46) { + ds.arpNonPorta=true; + ds.algMacroBehavior=true; + } else { + ds.arpNonPorta=false; + ds.algMacroBehavior=false; + } + if (ds.version<49) { + ds.brokenShortcutSlides=true; + } + if (ds.version<50) { + ds.ignoreDuplicateSlides=false; + } + if (ds.version<62) { + ds.stopPortaOnNoteOff=true; + } + if (ds.version<64) { + ds.brokenDACMode=false; + } + if (ds.version<65) { + ds.oneTickCut=false; + } + if (ds.version<66) { + ds.newInsTriggersInPorta=false; + } + if (ds.version<69) { + ds.arp0Reset=false; + } + if (ds.version<71) { + ds.noSlidesOnFirstTick=false; + ds.rowResetsArpPos=false; + ds.ignoreJumpAtEnd=true; + } + if (ds.version<72) { + ds.buggyPortaAfterSlide=true; + ds.gbInsAffectsEnvelope=false; + } + if (ds.version<78) { + ds.sharedExtStat=false; + } + if (ds.version<83) { + ds.ignoreDACModeOutsideIntendedChannel=true; + ds.e1e2AlsoTakePriority=false; + } + if (ds.version<84) { + ds.newSegaPCM=false; + } + if (ds.version<85) { + ds.fbPortaPause=true; + } + if (ds.version<86) { + ds.snDutyReset=true; + } + if (ds.version<90) { + ds.pitchMacroIsLinear=false; + } + if (ds.version<97) { + ds.oldOctaveBoundary=true; + } + if (ds.version<97) { // actually should be 98 but yky uses this feature ahead of time + ds.noOPN2Vol=true; + } + if (ds.version<99) { + ds.newVolumeScaling=false; + ds.volMacroLinger=false; + ds.brokenOutVol=true; + } + if (ds.version<100) { + ds.e1e2StopOnSameNote=false; + } + if (ds.version<101) { + ds.brokenPortaArp=true; + } + if (ds.version<108) { + ds.snNoLowPeriods=true; + } + if (ds.version<110) { + ds.delayBehavior=1; + } + if (ds.version<113) { + ds.jumpTreatment=1; + } + if (ds.version<115) { + ds.autoSystem=false; + } + if (ds.version<117) { + ds.disableSampleMacro=true; + } + if (ds.version<121) { + ds.brokenOutVol2=false; + } + if (ds.version<130) { + ds.oldArpStrategy=true; + } + if (ds.version<138) { + ds.brokenPortaLegato=true; + } + if (ds.version<155) { + ds.brokenFMOff=true; + } + if (ds.version<168) { + ds.preNoteNoEffect=true; + } + if (ds.version<183) { + ds.oldDPCM=true; + } + if (ds.version<184) { + ds.resetArpPhaseOnNewNote=false; + } + if (ds.version<188) { + ds.ceilVolumeScaling=false; + } + if (ds.version<191) { + ds.oldAlwaysSetVolume=true; + } + ds.isDMF=false; + + reader.readS(); // reserved + int infoSeek=reader.readI(); + + if (!reader.seek(infoSeek,SEEK_SET)) { + logE("couldn't seek to info header at %d!",infoSeek); + lastError="couldn't seek to info header!"; + delete[] file; + return false; + } + + // read header + reader.read(magic,4); + if (strcmp(magic,"INFO")!=0) { + logE("invalid info header!"); + lastError="invalid info header!"; + delete[] file; + return false; + } + reader.readI(); + + subSong->timeBase=reader.readC(); + subSong->speeds.len=2; + subSong->speeds.val[0]=reader.readC(); + subSong->speeds.val[1]=reader.readC(); + subSong->arpLen=reader.readC(); + subSong->hz=reader.readF(); + + subSong->patLen=reader.readS(); + subSong->ordersLen=reader.readS(); + + subSong->hilightA=reader.readC(); + subSong->hilightB=reader.readC(); + + ds.insLen=reader.readS(); + ds.waveLen=reader.readS(); + ds.sampleLen=reader.readS(); + int numberOfPats=reader.readI(); + + if (subSong->patLen<0) { + logE("pattern length is negative!"); + lastError="pattern lengrh is negative!"; + delete[] file; + return false; + } + if (subSong->patLen>DIV_MAX_ROWS) { + logE("pattern length is too large!"); + lastError="pattern length is too large!"; + delete[] file; + return false; + } + if (subSong->ordersLen<0) { + logE("song length is negative!"); + lastError="song length is negative!"; + delete[] file; + return false; + } + if (subSong->ordersLen>DIV_MAX_PATTERNS) { + logE("song is too long!"); + lastError="song is too long!"; + delete[] file; + return false; + } + if (ds.insLen<0 || ds.insLen>256) { + logE("invalid instrument count!"); + lastError="invalid instrument count!"; + delete[] file; + return false; + } + if (ds.waveLen<0 || ds.waveLen>256) { + logE("invalid wavetable count!"); + lastError="invalid wavetable count!"; + delete[] file; + return false; + } + if (ds.sampleLen<0 || ds.sampleLen>256) { + logE("invalid sample count!"); + lastError="invalid sample count!"; + delete[] file; + return false; + } + if (numberOfPats<0) { + logE("invalid pattern count!"); + lastError="invalid pattern count!"; + delete[] file; + return false; + } + + logD("systems:"); + ds.systemLen=0; + for (int i=0; iDIV_MAX_CHANS) { + tchans=DIV_MAX_CHANS; + logW("too many channels!"); + } + logV("system len: %d",ds.systemLen); + if (ds.systemLen<1) { + logE("zero chips!"); + lastError="zero chips!"; + delete[] file; + return false; + } + + // system volume + for (int i=0; ii; j--) { + ds.system[j]=ds.system[j-1]; + ds.systemVol[j]=ds.systemVol[j-1]; + ds.systemPan[j]=ds.systemPan[j-1]; + } + if (++ds.systemLen>DIV_MAX_CHIPS) ds.systemLen=DIV_MAX_CHIPS; + + if (ds.system[i]==DIV_SYSTEM_GENESIS) { + ds.system[i]=DIV_SYSTEM_YM2612; + if (i<31) { + ds.system[i+1]=DIV_SYSTEM_SMS; + ds.systemVol[i+1]=ds.systemVol[i]*0.375f; + } + } + if (ds.system[i]==DIV_SYSTEM_GENESIS_EXT) { + ds.system[i]=DIV_SYSTEM_YM2612_EXT; + if (i<31) { + ds.system[i+1]=DIV_SYSTEM_SMS; + ds.systemVol[i+1]=ds.systemVol[i]*0.375f; + } + } + if (ds.system[i]==DIV_SYSTEM_ARCADE) { + ds.system[i]=DIV_SYSTEM_YM2151; + if (i<31) { + ds.system[i+1]=DIV_SYSTEM_SEGAPCM_COMPAT; + } + } + i++; + } + } + + ds.name=reader.readString(); + ds.author=reader.readString(); + logI("%s by %s",ds.name.c_str(),ds.author.c_str()); + + if (ds.version>=33) { + ds.tuning=reader.readF(); + } else { + reader.readI(); + } + + // compatibility flags + if (ds.version>=37) { + ds.limitSlides=reader.readC(); + ds.linearPitch=reader.readC(); + ds.loopModality=reader.readC(); + if (ds.version>=43) { + ds.properNoiseLayout=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=43) { + ds.waveDutyIsVol=reader.readC(); + } else { + reader.readC(); + } + + if (ds.version>=45) { + ds.resetMacroOnPorta=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=45) { + ds.legacyVolumeSlides=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=45) { + ds.compatibleArpeggio=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=45) { + ds.noteOffResetsSlides=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=45) { + ds.targetResetsSlides=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=47) { + ds.arpNonPorta=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=47) { + ds.algMacroBehavior=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=49) { + ds.brokenShortcutSlides=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=50) { + ds.ignoreDuplicateSlides=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=62) { + ds.stopPortaOnNoteOff=reader.readC(); + ds.continuousVibrato=reader.readC(); + } else { + reader.readC(); + reader.readC(); + } + if (ds.version>=64) { + ds.brokenDACMode=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=65) { + ds.oneTickCut=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=66) { + ds.newInsTriggersInPorta=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=69) { + ds.arp0Reset=reader.readC(); + } else { + reader.readC(); + } + } else { + for (int i=0; i<20; i++) reader.readC(); + } + + // pointers + for (int i=0; iordersLen); + for (int i=0; iordersLen; j++) { + subSong->orders.ord[i][j]=reader.readC(); + } + } + + for (int i=0; ipat[i].effectCols=reader.readC(); + if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>DIV_MAX_EFFECTS) { + logE("channel %d has zero or too many effect columns! (%d)",i,subSong->pat[i].effectCols); + lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,subSong->pat[i].effectCols); + delete[] file; + return false; + } + } + + if (ds.version>=39) { + for (int i=0; ichanShow[i]=reader.readC(); + subSong->chanShowChanOsc[i]=subSong->chanShow[i]; + } else { + unsigned char tempchar=reader.readC(); + subSong->chanShow[i]=tempchar&1; + subSong->chanShowChanOsc[i]=(tempchar&2); + } + } + + for (int i=0; ichanCollapse[i]=reader.readC(); + } + + if (ds.version<92) { + for (int i=0; ichanCollapse[i]>0) subSong->chanCollapse[i]=3; + } + } + + for (int i=0; ichanName[i]=reader.readString(); + } + + for (int i=0; ichanShortName[i]=reader.readString(); + } + + ds.notes=reader.readString(); + } + + if (ds.version>=59) { + ds.masterVol=reader.readF(); + } else { + ds.masterVol=2.0f; + } + + if (ds.version>=70) { + // extended compat flags + ds.brokenSpeedSel=reader.readC(); + if (ds.version>=71) { + ds.noSlidesOnFirstTick=reader.readC(); + ds.rowResetsArpPos=reader.readC(); + ds.ignoreJumpAtEnd=reader.readC(); + } else { + reader.readC(); + reader.readC(); + reader.readC(); + } + if (ds.version>=72) { + ds.buggyPortaAfterSlide=reader.readC(); + ds.gbInsAffectsEnvelope=reader.readC(); + } else { + reader.readC(); + reader.readC(); + } + if (ds.version>=78) { + ds.sharedExtStat=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=83) { + ds.ignoreDACModeOutsideIntendedChannel=reader.readC(); + ds.e1e2AlsoTakePriority=reader.readC(); + } else { + reader.readC(); + reader.readC(); + } + if (ds.version>=84) { + ds.newSegaPCM=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=85) { + ds.fbPortaPause=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=86) { + ds.snDutyReset=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=90) { + ds.pitchMacroIsLinear=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=94) { + ds.pitchSlideSpeed=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=97) { + ds.oldOctaveBoundary=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=98) { + ds.noOPN2Vol=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=99) { + ds.newVolumeScaling=reader.readC(); + ds.volMacroLinger=reader.readC(); + ds.brokenOutVol=reader.readC(); + } else { + reader.readC(); + reader.readC(); + reader.readC(); + } + if (ds.version>=100) { + ds.e1e2StopOnSameNote=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=101) { + ds.brokenPortaArp=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=108) { + ds.snNoLowPeriods=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=110) { + ds.delayBehavior=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=113) { + ds.jumpTreatment=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=115) { + ds.autoSystem=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=117) { + ds.disableSampleMacro=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=121) { + ds.brokenOutVol2=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=130) { + ds.oldArpStrategy=reader.readC(); + } else { + reader.readC(); + } + } + + // first song virtual tempo + if (ds.version>=96) { + subSong->virtualTempoN=reader.readS(); + subSong->virtualTempoD=reader.readS(); + } else { + reader.readI(); + } + + // subsongs + if (ds.version>=95) { + subSong->name=reader.readString(); + subSong->notes=reader.readString(); + numberOfSubSongs=(unsigned char)reader.readC(); + reader.readC(); // reserved + reader.readC(); + reader.readC(); + // pointers + for (int i=0; i=103) { + ds.systemName=reader.readString(); + ds.category=reader.readString(); + ds.nameJ=reader.readString(); + ds.authorJ=reader.readString(); + ds.systemNameJ=reader.readString(); + ds.categoryJ=reader.readString(); + } else { + ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0)); + ds.autoSystem=true; + } + + // system output config + if (ds.version>=135) { + for (int i=0; i0) ds.patchbay.reserve(conns); + for (unsigned int i=0; i=136) ds.patchbayAuto=reader.readC(); + + if (ds.version>=138) { + ds.brokenPortaLegato=reader.readC(); + if (ds.version>=155) { + ds.brokenFMOff=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=168) { + ds.preNoteNoEffect=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=183) { + ds.oldDPCM=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=184) { + ds.resetArpPhaseOnNewNote=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=188) { + ds.ceilVolumeScaling=reader.readC(); + } else { + reader.readC(); + } + if (ds.version>=191) { + ds.oldAlwaysSetVolume=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<1; i++) { + reader.readC(); + } + } + + if (ds.version>=139) { + subSong->speeds.len=reader.readC(); + for (int i=0; i<16; i++) { + subSong->speeds.val[i]=reader.readC(); + } + + // grooves + unsigned char grooveCount=reader.readC(); + ds.grooves.reserve(grooveCount); + for (int i=0; i=156) { + assetDirPtr[0]=reader.readI(); + assetDirPtr[1]=reader.readI(); + assetDirPtr[2]=reader.readI(); + } + + // read system flags + if (ds.version>=119) { + logD("reading chip flags..."); + for (int i=0; i=156) { + logD("reading asset directories..."); + + if (!reader.seek(assetDirPtr[0],SEEK_SET)) { + logE("couldn't seek to ins dir!"); + lastError=fmt::sprintf("couldn't read instrument directory"); + ds.unload(); + delete[] file; + return false; + } + if (readAssetDirData(reader,ds.insDir)!=DIV_DATA_SUCCESS) { + lastError="invalid instrument directory data!"; + ds.unload(); + delete[] file; + return false; + } + + if (!reader.seek(assetDirPtr[1],SEEK_SET)) { + logE("couldn't seek to wave dir!"); + lastError=fmt::sprintf("couldn't read wavetable directory"); + ds.unload(); + delete[] file; + return false; + } + if (readAssetDirData(reader,ds.waveDir)!=DIV_DATA_SUCCESS) { + lastError="invalid wavetable directory data!"; + ds.unload(); + delete[] file; + return false; + } + + if (!reader.seek(assetDirPtr[2],SEEK_SET)) { + logE("couldn't seek to sample dir!"); + lastError=fmt::sprintf("couldn't read sample directory"); + ds.unload(); + delete[] file; + return false; + } + if (readAssetDirData(reader,ds.sampleDir)!=DIV_DATA_SUCCESS) { + lastError="invalid sample directory data!"; + ds.unload(); + delete[] file; + return false; + } + } + + // read subsongs + if (ds.version>=95) { + ds.subsong.reserve(numberOfSubSongs); + for (int i=0; itimeBase=reader.readC(); + subSong->speeds.len=2; + subSong->speeds.val[0]=reader.readC(); + subSong->speeds.val[1]=reader.readC(); + subSong->arpLen=reader.readC(); + subSong->hz=reader.readF(); + + subSong->patLen=reader.readS(); + subSong->ordersLen=reader.readS(); + + subSong->hilightA=reader.readC(); + subSong->hilightB=reader.readC(); + + if (ds.version>=96) { + subSong->virtualTempoN=reader.readS(); + subSong->virtualTempoD=reader.readS(); + } else { + reader.readI(); + } + + subSong->name=reader.readString(); + subSong->notes=reader.readString(); + + logD("reading orders of subsong %d (%d)...",i+1,subSong->ordersLen); + for (int j=0; jordersLen; k++) { + subSong->orders.ord[j][k]=reader.readC(); + } + } + + for (int i=0; ipat[i].effectCols=reader.readC(); + } + + for (int i=0; ichanShow[i]=reader.readC(); + subSong->chanShowChanOsc[i]=subSong->chanShow[i]; + } else { + unsigned char tempchar=reader.readC(); + subSong->chanShow[i]=tempchar&1; + subSong->chanShowChanOsc[i]=tempchar&2; + } + } + + for (int i=0; ichanCollapse[i]=reader.readC(); + } + + for (int i=0; ichanName[i]=reader.readString(); + } + + for (int i=0; ichanShortName[i]=reader.readString(); + } + + if (ds.version>=139) { + subSong->speeds.len=reader.readC(); + for (int i=0; i<16; i++) { + subSong->speeds.val[i]=reader.readC(); + } + } + } + } + + // read instruments + ds.ins.reserve(ds.insLen); + for (int i=0; ireadInsData(reader,ds.version)!=DIV_DATA_SUCCESS) { + lastError="invalid instrument header/data!"; + ds.unload(); + delete ins; + delete[] file; + return false; + } + + ds.ins.push_back(ins); + } + + // read wavetables + ds.wave.reserve(ds.waveLen); + for (int i=0; ireadWaveData(reader,ds.version)!=DIV_DATA_SUCCESS) { + lastError="invalid wavetable header/data!"; + ds.unload(); + delete wave; + delete[] file; + return false; + } + + ds.wave.push_back(wave); + } + + // read samples + ds.sample.reserve(ds.sampleLen); + for (int i=0; ireadSampleData(reader,ds.version)!=DIV_DATA_SUCCESS) { + lastError="invalid sample header/data!"; + ds.unload(); + delete sample; + delete[] file; + return false; + } + + ds.sample.push_back(sample); + } + + // read patterns + for (unsigned int i: patPtr) { + bool isNewFormat=false; + if (!reader.seek(i,SEEK_SET)) { + logE("couldn't seek to pattern in %x!",i); + lastError=fmt::sprintf("couldn't seek to pattern in %x!",i); + ds.unload(); + delete[] file; + return false; + } + reader.read(magic,4); + logD("reading pattern in %x...",i); + if (strcmp(magic,"PATR")!=0) { + if (strcmp(magic,"PATN")!=0 || ds.version<157) { + logE("%x: invalid pattern header!",i); + lastError="invalid pattern header!"; + ds.unload(); + delete[] file; + return false; + } else { + isNewFormat=true; + } + } + reader.readI(); + + if (isNewFormat) { + int subs=(unsigned char)reader.readC(); + int chan=(unsigned char)reader.readC(); + int index=reader.readS(); + + logD("- %d, %d, %d (new)",subs,chan,index); + + if (chan<0 || chan>=tchans) { + logE("pattern channel out of range!",i); + lastError="pattern channel out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (index<0 || index>(DIV_MAX_PATTERNS-1)) { + logE("pattern index out of range!",i); + lastError="pattern index out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (subs<0 || subs>=(int)ds.subsong.size()) { + logE("pattern subsong out of range!",i); + lastError="pattern subsong out of range!"; + ds.unload(); + delete[] file; + return false; + } + + DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true); + pat->name=reader.readString(); + + // read new pattern + for (int j=0; jpatLen; j++) { + unsigned char mask=reader.readC(); + unsigned short effectMask=0; + + if (mask==0xff) break; + if (mask&128) { + j+=(mask&127)+1; + continue; + } + + if (mask&32) { + effectMask|=(unsigned char)reader.readC(); + } + if (mask&64) { + effectMask|=((unsigned short)reader.readC()&0xff)<<8; + } + if (mask&8) effectMask|=1; + if (mask&16) effectMask|=2; + + if (mask&1) { // note + unsigned char note=reader.readC(); + if (note==180) { + pat->data[j][0]=100; + pat->data[j][1]=0; + } else if (note==181) { + pat->data[j][0]=101; + pat->data[j][1]=0; + } else if (note==182) { + pat->data[j][0]=102; + pat->data[j][1]=0; + } else if (note<180) { + pat->data[j][0]=newFormatNotes[note]; + pat->data[j][1]=newFormatOctaves[note]; + } else { + pat->data[j][0]=0; + pat->data[j][1]=0; + } + } + if (mask&2) { // instrument + pat->data[j][2]=(unsigned char)reader.readC(); + } + if (mask&4) { // volume + pat->data[j][3]=(unsigned char)reader.readC(); + } + for (unsigned char k=0; k<16; k++) { + if (effectMask&(1<data[j][4+k]=(unsigned char)reader.readC(); + } + } + } + } else { + int chan=reader.readS(); + int index=reader.readS(); + int subs=0; + if (ds.version>=95) { + subs=reader.readS(); + } else { + reader.readS(); + } + reader.readS(); + + logD("- %d, %d, %d (old)",subs,chan,index); + + if (chan<0 || chan>=tchans) { + logE("pattern channel out of range!",i); + lastError="pattern channel out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (index<0 || index>(DIV_MAX_PATTERNS-1)) { + logE("pattern index out of range!",i); + lastError="pattern index out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (subs<0 || subs>=(int)ds.subsong.size()) { + logE("pattern subsong out of range!",i); + lastError="pattern subsong out of range!"; + ds.unload(); + delete[] file; + return false; + } + + DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true); + for (int j=0; jpatLen; j++) { + pat->data[j][0]=reader.readS(); + pat->data[j][1]=reader.readS(); + pat->data[j][2]=reader.readS(); + pat->data[j][3]=reader.readS(); + for (int k=0; kpat[chan].effectCols; k++) { + pat->data[j][4+(k<<1)]=reader.readS(); + pat->data[j][5+(k<<1)]=reader.readS(); + } + if (pat->data[j][0]==0 && pat->data[j][1]!=0) { + logD("what? %d:%d:%d note %d octave %d",chan,i,j,pat->data[j][0],pat->data[j][1]); + pat->data[j][0]=12; + pat->data[j][1]--; + } + } + + if (ds.version>=51) { + pat->name=reader.readString(); + } + } + } + + if (reader.tell()opnCount) { + for (DivInstrument* i: ds.ins) { + if (i->type==DIV_INS_FM) i->type=DIV_INS_OPM; + } + } + if (nesCount>snCount) { + for (DivInstrument* i: ds.ins) { + if (i->type==DIV_INS_STD) i->type=DIV_INS_NES; + } + } + } + + // ExtCh compat flag + for (int i=0; i subSongPtr; + std::vector sysFlagsPtr; + std::vector insPtr; + std::vector wavePtr; + std::vector samplePtr; + std::vector patPtr; + int assetDirPtr[3]; + size_t ptrSeek, subSongPtrSeek, sysFlagsPtrSeek, blockStartSeek, blockEndSeek, assetDirPtrSeek; + size_t subSongIndex=0; + DivSubSong* subSong=song.subsong[subSongIndex]; + warnings=""; + + // fail if values are out of range + /* + if (subSong->ordersLen>DIV_MAX_PATTERNS) { + logE("maximum song length is %d!",DIV_MAX_PATTERNS); + lastError=fmt::sprintf("maximum song length is %d",DIV_MAX_PATTERNS); + return NULL; + } + if (subSong->patLen>DIV_MAX_ROWS) { + logE("maximum pattern length is %d!",DIV_MAX_ROWS); + lastError=fmt::sprintf("maximum pattern length is %d",DIV_MAX_ROWS); + return NULL; + } + */ + if (song.ins.size()>256) { + logE("maximum number of instruments is 256!"); + lastError="maximum number of instruments is 256"; + saveLock.unlock(); + return NULL; + } + if (song.wave.size()>256) { + logE("maximum number of wavetables is 256!"); + lastError="maximum number of wavetables is 256"; + saveLock.unlock(); + return NULL; + } + if (song.sample.size()>256) { + logE("maximum number of samples is 256!"); + lastError="maximum number of samples is 256"; + saveLock.unlock(); + return NULL; + } + + if (!notPrimary) { + song.isDMF=false; + song.version=DIV_ENGINE_VERSION; + } + + SafeWriter* w=new SafeWriter; + w->init(); + /// HEADER + // write magic + w->write(DIV_FUR_MAGIC,16); + + // write version + w->writeS(DIV_ENGINE_VERSION); + + // reserved + w->writeS(0); + + // song info pointer + w->writeI(32); + + // reserved + w->writeI(0); + w->writeI(0); + + // high short is channel + // low short is pattern number + std::vector patsToWrite; + if (getConfInt("saveUnusedPatterns",0)==1) { + for (int i=0; ipat[i].data[k]==NULL) continue; + patsToWrite.push_back(PatToWrite(j,i,k)); + } + } + } + } else { + bool alreadyAdded[DIV_MAX_PATTERNS]; + for (int i=0; iordersLen; k++) { + if (alreadyAdded[subs->orders.ord[i][k]]) continue; + patsToWrite.push_back(PatToWrite(j,i,subs->orders.ord[i][k])); + alreadyAdded[subs->orders.ord[i][k]]=true; + } + } + } + } + + /// SONG INFO + w->write("INFO",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeC(subSong->timeBase); + // these are for compatibility + w->writeC(subSong->speeds.val[0]); + w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]); + w->writeC(subSong->arpLen); + w->writeF(subSong->hz); + w->writeS(subSong->patLen); + w->writeS(subSong->ordersLen); + w->writeC(subSong->hilightA); + w->writeC(subSong->hilightB); + w->writeS(song.insLen); + w->writeS(song.waveLen); + w->writeS(song.sampleLen); + w->writeI(patsToWrite.size()); + + for (int i=0; i=song.systemLen) { + w->writeC(0); + } else { + w->writeC(systemToFileFur(song.system[i])); + } + } + + for (int i=0; iwriteC(song.systemVol[i]*64.0f); + } + + for (int i=0; iwriteC(song.systemPan[i]*127.0f); + } + + // chip flags (we'll seek here later) + sysFlagsPtrSeek=w->tell(); + for (int i=0; iwriteI(0); + } + + // song name + w->writeString(song.name,false); + // song author + w->writeString(song.author,false); + + w->writeF(song.tuning); + + // compatibility flags + w->writeC(song.limitSlides); + w->writeC(song.linearPitch); + w->writeC(song.loopModality); + w->writeC(song.properNoiseLayout); + w->writeC(song.waveDutyIsVol); + w->writeC(song.resetMacroOnPorta); + w->writeC(song.legacyVolumeSlides); + w->writeC(song.compatibleArpeggio); + w->writeC(song.noteOffResetsSlides); + w->writeC(song.targetResetsSlides); + w->writeC(song.arpNonPorta); + w->writeC(song.algMacroBehavior); + w->writeC(song.brokenShortcutSlides); + w->writeC(song.ignoreDuplicateSlides); + w->writeC(song.stopPortaOnNoteOff); + w->writeC(song.continuousVibrato); + w->writeC(song.brokenDACMode); + w->writeC(song.oneTickCut); + w->writeC(song.newInsTriggersInPorta); + w->writeC(song.arp0Reset); + + ptrSeek=w->tell(); + // instrument pointers (we'll seek here later) + for (int i=0; iwriteI(0); + } + + // wavetable pointers (we'll seek here later) + for (int i=0; iwriteI(0); + } + + // sample pointers (we'll seek here later) + for (int i=0; iwriteI(0); + } + + // pattern pointers (we'll seek here later) + for (size_t i=0; iwriteI(0); + } + + for (int i=0; iordersLen; j++) { + w->writeC(subSong->orders.ord[i][j]); + } + } + + for (int i=0; iwriteC(subSong->pat[i].effectCols); + } + + for (int i=0; iwriteC( + (subSong->chanShow[i]?1:0)| + (subSong->chanShowChanOsc[i]?2:0) + ); + } + + for (int i=0; iwriteC(subSong->chanCollapse[i]); + } + + for (int i=0; iwriteString(subSong->chanName[i],false); + } + + for (int i=0; iwriteString(subSong->chanShortName[i],false); + } + + w->writeString(song.notes,false); + + w->writeF(song.masterVol); + + // extended compat flags + w->writeC(song.brokenSpeedSel); + w->writeC(song.noSlidesOnFirstTick); + w->writeC(song.rowResetsArpPos); + w->writeC(song.ignoreJumpAtEnd); + w->writeC(song.buggyPortaAfterSlide); + w->writeC(song.gbInsAffectsEnvelope); + w->writeC(song.sharedExtStat); + w->writeC(song.ignoreDACModeOutsideIntendedChannel); + w->writeC(song.e1e2AlsoTakePriority); + w->writeC(song.newSegaPCM); + w->writeC(song.fbPortaPause); + w->writeC(song.snDutyReset); + w->writeC(song.pitchMacroIsLinear); + w->writeC(song.pitchSlideSpeed); + w->writeC(song.oldOctaveBoundary); + w->writeC(song.noOPN2Vol); + w->writeC(song.newVolumeScaling); + w->writeC(song.volMacroLinger); + w->writeC(song.brokenOutVol); + w->writeC(song.e1e2StopOnSameNote); + w->writeC(song.brokenPortaArp); + w->writeC(song.snNoLowPeriods); + w->writeC(song.delayBehavior); + w->writeC(song.jumpTreatment); + w->writeC(song.autoSystem); + w->writeC(song.disableSampleMacro); + w->writeC(song.brokenOutVol2); + w->writeC(song.oldArpStrategy); + + // first subsong virtual tempo + w->writeS(subSong->virtualTempoN); + w->writeS(subSong->virtualTempoD); + + // subsong list + w->writeString(subSong->name,false); + w->writeString(subSong->notes,false); + w->writeC((unsigned char)(song.subsong.size()-1)); + w->writeC(0); // reserved + w->writeC(0); + w->writeC(0); + subSongPtrSeek=w->tell(); + // subsong pointers (we'll seek here later) + for (size_t i=0; i<(song.subsong.size()-1); i++) { + w->writeI(0); + } + + // additional metadata + w->writeString(song.systemName,false); + w->writeString(song.category,false); + w->writeString(song.nameJ,false); + w->writeString(song.authorJ,false); + w->writeString(song.systemNameJ,false); + w->writeString(song.categoryJ,false); + + // system output config + for (int i=0; iwriteF(song.systemVol[i]); + w->writeF(song.systemPan[i]); + w->writeF(song.systemPanFR[i]); + } + w->writeI(song.patchbay.size()); + for (unsigned int i: song.patchbay) { + w->writeI(i); + } + w->writeC(song.patchbayAuto); + + // even more compat flags + w->writeC(song.brokenPortaLegato); + w->writeC(song.brokenFMOff); + w->writeC(song.preNoteNoEffect); + w->writeC(song.oldDPCM); + w->writeC(song.resetArpPhaseOnNewNote); + w->writeC(song.ceilVolumeScaling); + w->writeC(song.oldAlwaysSetVolume); + for (int i=0; i<1; i++) { + w->writeC(0); + } + + // speeds of first song + w->writeC(subSong->speeds.len); + for (int i=0; i<16; i++) { + w->writeC(subSong->speeds.val[i]); + } + + // groove list + w->writeC((unsigned char)song.grooves.size()); + for (const DivGroovePattern& i: song.grooves) { + w->writeC(i.len); + for (int j=0; j<16; j++) { + w->writeC(i.val[j]); + } + } + + // asset dir pointers (we'll seek here later) + assetDirPtrSeek=w->tell(); + w->writeI(0); + w->writeI(0); + w->writeI(0); + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); + + /// SUBSONGS + subSongPtr.reserve(song.subsong.size() - 1); + for (subSongIndex=1; subSongIndextell()); + w->write("SONG",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeC(subSong->timeBase); + w->writeC(subSong->speeds.val[0]); + w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]); + w->writeC(subSong->arpLen); + w->writeF(subSong->hz); + w->writeS(subSong->patLen); + w->writeS(subSong->ordersLen); + w->writeC(subSong->hilightA); + w->writeC(subSong->hilightB); + w->writeS(subSong->virtualTempoN); + w->writeS(subSong->virtualTempoD); + + w->writeString(subSong->name,false); + w->writeString(subSong->notes,false); + + for (int i=0; iordersLen; j++) { + w->writeC(subSong->orders.ord[i][j]); + } + } + + for (int i=0; iwriteC(subSong->pat[i].effectCols); + } + + for (int i=0; iwriteC( + (subSong->chanShow[i]?1:0)| + (subSong->chanShowChanOsc[i]?2:0) + ); + } + + for (int i=0; iwriteC(subSong->chanCollapse[i]); + } + + for (int i=0; iwriteString(subSong->chanName[i],false); + } + + for (int i=0; iwriteString(subSong->chanShortName[i],false); + } + + // speeds + w->writeC(subSong->speeds.len); + for (int i=0; i<16; i++) { + w->writeC(subSong->speeds.val[i]); + } + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); + } + + /// CHIP FLAGS + sysFlagsPtr.reserve(song.systemLen); + for (int i=0; itell()); + w->write("FLAG",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeString(data,false); + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); + } + + /// ASSET DIRECTORIES + assetDirPtr[0]=w->tell(); + putAssetDirData(w,song.insDir); + assetDirPtr[1]=w->tell(); + putAssetDirData(w,song.waveDir); + assetDirPtr[2]=w->tell(); + putAssetDirData(w,song.sampleDir); + + /// INSTRUMENT + insPtr.reserve(song.insLen); + for (int i=0; itell()); + ins->putInsData2(w,false); + } + + /// WAVETABLE + wavePtr.reserve(song.waveLen); + for (int i=0; itell()); + wave->putWaveData(w); + } + + /// SAMPLE + samplePtr.reserve(song.sampleLen); + for (int i=0; itell()); + sample->putSampleData(w); + } + + /// PATTERN + patPtr.reserve(patsToWrite.size()); + for (PatToWrite& i: patsToWrite) { + DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false); + patPtr.push_back(w->tell()); + + if (newPatternFormat) { + w->write("PATN",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeC(i.subsong); + w->writeC(i.chan); + w->writeS(i.pat); + w->writeString(pat->name,false); + + unsigned char emptyRows=0; + + for (int j=0; jpatLen; j++) { + unsigned char mask=0; + unsigned char finalNote=255; + unsigned short effectMask=0; + + if (pat->data[j][0]==100) { + finalNote=180; + } else if (pat->data[j][0]==101) { // note release + finalNote=181; + } else if (pat->data[j][0]==102) { // macro release + finalNote=182; + } else if (pat->data[j][1]==0 && pat->data[j][0]==0) { + finalNote=255; + } else { + int seek=(pat->data[j][0]+(signed char)pat->data[j][1]*12)+60; + if (seek<0 || seek>=180) { + finalNote=255; + } else { + finalNote=seek; + } + } + + if (finalNote!=255) mask|=1; // note + if (pat->data[j][2]!=-1) mask|=2; // instrument + if (pat->data[j][3]!=-1) mask|=4; // volume + for (int k=0; kpat[i.chan].effectCols*2; k+=2) { + if (k==0) { + if (pat->data[j][4+k]!=-1) mask|=8; + if (pat->data[j][5+k]!=-1) mask|=16; + } else if (k<8) { + if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=32; + } else { + if (pat->data[j][4+k]!=-1 || pat->data[j][5+k]!=-1) mask|=64; + } + + if (pat->data[j][4+k]!=-1) effectMask|=(1<data[j][5+k]!=-1) effectMask|=(2<127) { + w->writeC(128|(emptyRows-2)); + emptyRows=0; + } + } else { + if (emptyRows>1) { + w->writeC(128|(emptyRows-2)); + emptyRows=0; + } else if (emptyRows) { + w->writeC(0); + emptyRows=0; + } + + w->writeC(mask); + + if (mask&32) w->writeC(effectMask&0xff); + if (mask&64) w->writeC((effectMask>>8)&0xff); + + if (mask&1) w->writeC(finalNote); + if (mask&2) w->writeC(pat->data[j][2]); + if (mask&4) w->writeC(pat->data[j][3]); + if (mask&8) w->writeC(pat->data[j][4]); + if (mask&16) w->writeC(pat->data[j][5]); + if (mask&32) { + if (effectMask&4) w->writeC(pat->data[j][6]); + if (effectMask&8) w->writeC(pat->data[j][7]); + if (effectMask&16) w->writeC(pat->data[j][8]); + if (effectMask&32) w->writeC(pat->data[j][9]); + if (effectMask&64) w->writeC(pat->data[j][10]); + if (effectMask&128) w->writeC(pat->data[j][11]); + } + if (mask&64) { + if (effectMask&256) w->writeC(pat->data[j][12]); + if (effectMask&512) w->writeC(pat->data[j][13]); + if (effectMask&1024) w->writeC(pat->data[j][14]); + if (effectMask&2048) w->writeC(pat->data[j][15]); + if (effectMask&4096) w->writeC(pat->data[j][16]); + if (effectMask&8192) w->writeC(pat->data[j][17]); + if (effectMask&16384) w->writeC(pat->data[j][18]); + if (effectMask&32768) w->writeC(pat->data[j][19]); + } + } + } + + // stop + w->writeC(0xff); + } else { + w->write("PATR",4); + blockStartSeek=w->tell(); + w->writeI(0); + + w->writeS(i.chan); + w->writeS(i.pat); + w->writeS(i.subsong); + + w->writeS(0); // reserved + + for (int j=0; jpatLen; j++) { + w->writeS(pat->data[j][0]); // note + w->writeS(pat->data[j][1]); // octave + w->writeS(pat->data[j][2]); // instrument + w->writeS(pat->data[j][3]); // volume +#ifdef TA_BIG_ENDIAN + for (int k=0; kpat[i.chan].effectCols*2; k++) { + w->writeS(pat->data[j][4+k]); + } +#else + w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects +#endif + } + + w->writeString(pat->name,false); + } + + blockEndSeek=w->tell(); + w->seek(blockStartSeek,SEEK_SET); + w->writeI(blockEndSeek-blockStartSeek-4); + w->seek(0,SEEK_END); + } + + /// POINTERS + w->seek(ptrSeek,SEEK_SET); + + for (int i=0; iwriteI(insPtr[i]); + } + + // wavetable pointers + for (int i=0; iwriteI(wavePtr[i]); + } + + // sample pointers + for (int i=0; iwriteI(samplePtr[i]); + } + + // pattern pointers + for (int i: patPtr) { + w->writeI(i); + } + + // subsong pointers + w->seek(subSongPtrSeek,SEEK_SET); + for (size_t i=0; i<(song.subsong.size()-1); i++) { + w->writeI(subSongPtr[i]); + } + + // flag pointers + w->seek(sysFlagsPtrSeek,SEEK_SET); + for (size_t i=0; iwriteI(sysFlagsPtr[i]); + } + + // asset dir pointers + w->seek(assetDirPtrSeek,SEEK_SET); + for (size_t i=0; i<3; i++) { + w->writeI(assetDirPtr[i]); + } + + saveLock.unlock(); + return w; +} + diff --git a/src/engine/fileOps/mod.cpp b/src/engine/fileOps/mod.cpp new file mode 100644 index 000000000..02cecc9fd --- /dev/null +++ b/src/engine/fileOps/mod.cpp @@ -0,0 +1,447 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "fileOpsCommon.h" + +bool DivEngine::loadMod(unsigned char* file, size_t len) { + struct InvalidHeaderException {}; + bool success=false; + int chCount=0; + int ordCount=0; + std::vector 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); + ds.sample.reserve(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=(unsigned char)reader.readC(); + if (ds.subsong[0]->ordersLen<1 || ds.subsong[0]->ordersLen>128) { + 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]->chanShowChanOsc[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; + ds.subsong[0]->chanShowChanOsc[i]=false; + } + + // instrument creation + ds.ins.reserve(insCount); + 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; +} + diff --git a/src/engine/fileOps/s3m.cpp b/src/engine/fileOps/s3m.cpp new file mode 100644 index 000000000..fcebea9b7 --- /dev/null +++ b/src/engine/fileOps/s3m.cpp @@ -0,0 +1,261 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "fileOpsCommon.h" + +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 + ds.ins.reserve(ds.insLen); + 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; +} +