furnace/src/engine/fileOps.cpp
tildearrow f8187b9a5f dev191 - kind of remove DIV_ALWAYS_SET_VOLUME
it's useless crap I put during the Defle compat days
it serves nearly no purpose nowadays

also why is it a command?
2024-01-16 23:54:32 -05:00

6702 lines
196 KiB
C++

/**
* 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 <zlib.h>
#include <fmt/printf.h>
#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; i<DIV_MAX_CHANS; i++) {
historicColIns[i]=i;
}
ds.nullWave.len=32;
for (int i=0; i<32; i++) {
ds.nullWave.data[i]=15;
}
ds.isDMF=true;
if (!reader.seek(16,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete[] file;
return false;
}
ds.version=(unsigned char)reader.readC();
logI("module version %d (0x%.2x)",ds.version,ds.version);
if (ds.version>0x1b) {
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; i<getChannelCount(ds.system[0]); i++) {
for (int j=0; j<ds.subsong[0]->ordersLen; 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; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument;
unsigned char mode=0;
if (ds.version>0x05) {
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; j<ins->fm.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; j<ins->std.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; j<ins->std.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; j<ins->std.arpMacro.len; j++) {
ins->std.arpMacro.val[j]-=12;
}
} else {
ins->std.arpMacro.mode=0;
for (int j=0; j<ins->std.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; j<ins->std.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; j<ins->std.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; j<ins->std.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; j<ins->std.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]<ins->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; i<ds.waveLen; i++) {
DivWavetable* wave=new DivWavetable;
wave->len=(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; j<wave->len; 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; j<wave->len; 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; i<getChannelCount(ds.system[0]); i++) {
DivChannelData& chan=ds.subsong[0]->pat[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; j<ds.subsong[0]->ordersLen; j++) {
DivPattern* pat=chan.getPattern(ds.subsong[0]->orders.ord[i][j],true);
if (ds.version>0x08) { // current pattern format
for (int k=0; k<ds.subsong[0]->patLen; 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; l<chan.effectCols; l++) {
// effect
pat->data[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]<ds.insLen) {
ds.ins[pat->data[k][2]]->type=DIV_INS_FDS;
}
}
}
}
} else { // historic pattern format
if (i<16) pat->data[0][2]=historicColIns[i];
for (int k=0; k<ds.subsong[0]->patLen; 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; i<ds.sampleLen; i++) {
DivSample* sample=new DivSample;
int length=reader.readI();
int cutStart=0;
int cutEnd=length;
int pitch=5;
int vol=50;
short* data;
unsigned char* adpcmData;
if (length<0) {
logE("invalid sample length %d. are we doing something wrong?",length);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (ds.version>0x16) {
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<length; pos++) {
data[pos]=(short)((((unsigned short)data[pos])<<8)|(((unsigned short)data[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<length; j+=samplePitches[pitch]) {
if (k>=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 (cutEnd<cutStart) {
logE("cutEnd %d is before cutStart %d. what's going on?",cutEnd,cutStart);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutStart!=0 || cutEnd!=scaledLen) {
// cut data
short* newData=new short[cutEnd-cutStart];
memcpy(newData,&data[cutStart],(cutEnd-cutStart)*sizeof(short));
delete[] data;
data=newData;
scaledLen=cutEnd-cutStart;
cutStart=0;
cutEnd=scaledLen;
}
}
// copy data
if (!sample->init(scaledLen)) {
logE("%d: error while initializing sample!",i);
} else {
for (int i=0; i<scaledLen; i++) {
if (sample->depth==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<length; i++) {
adpcmData[i]=(adpcmData[i]<<4)|(adpcmData[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()<reader.size()) {
if ((reader.tell()+1)!=reader.size()) {
logW("premature end of song (we are at %x, but size is %x)",reader.tell(),reader.size());
}
}
// handle compound systems
if (ds.system[0]==DIV_SYSTEM_GENESIS) {
ds.systemLen=2;
ds.system[0]=DIV_SYSTEM_YM2612;
ds.system[1]=DIV_SYSTEM_SMS;
ds.systemVol[1]=0.5f;
}
if (ds.system[0]==DIV_SYSTEM_GENESIS_EXT) {
ds.systemLen=2;
ds.system[0]=DIV_SYSTEM_YM2612_EXT;
ds.system[1]=DIV_SYSTEM_SMS;
ds.systemVol[1]=0.5f;
}
if (ds.system[0]==DIV_SYSTEM_ARCADE) {
ds.systemLen=2;
ds.system[0]=DIV_SYSTEM_YM2151;
ds.system[1]=DIV_SYSTEM_SEGAPCM_COMPAT;
}
if (ds.system[0]==DIV_SYSTEM_SMS_OPLL) {
ds.systemLen=2;
ds.system[0]=DIV_SYSTEM_SMS;
ds.system[1]=DIV_SYSTEM_OPLL;
}
if (ds.system[0]==DIV_SYSTEM_NES_VRC7) {
ds.systemLen=2;
ds.system[0]=DIV_SYSTEM_NES;
ds.system[1]=DIV_SYSTEM_VRC7;
}
if (ds.system[0]==DIV_SYSTEM_NES_FDS) {
ds.systemLen=2;
ds.system[0]=DIV_SYSTEM_NES;
ds.system[1]=DIV_SYSTEM_FDS;
}
// SMS noise freq
if (ds.system[0]==DIV_SYSTEM_SMS) {
ds.systemFlags[0].set("noEasyNoise",true);
}
// NES PCM
if (ds.system[0]==DIV_SYSTEM_NES) {
ds.systemFlags[0].set("dpcmMode",false);
}
// C64 no key priority, reset time and multiply relative
if (ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) {
ds.systemFlags[0].set("keyPriority",false);
ds.systemFlags[0].set("initResetTime",1);
ds.systemFlags[0].set("multiplyRel",true);
}
// OPM broken pitch
if (ds.system[0]==DIV_SYSTEM_YM2151) {
ds.systemFlags[0].set("brokenPitch",true);
}
ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0));
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;
}
void DivEngine::convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivSystem sys) {
newFlags.clear();
switch (sys) {
case DIV_SYSTEM_SMS:
switch (oldFlags&0xff03) {
case 0x0000:
newFlags.set("clockSel",0);
break;
case 0x0001:
newFlags.set("clockSel",1);
break;
case 0x0002:
newFlags.set("clockSel",2);
break;
case 0x0003:
newFlags.set("clockSel",3);
break;
case 0x0100:
newFlags.set("clockSel",4);
break;
case 0x0101:
newFlags.set("clockSel",5);
break;
case 0x0102:
newFlags.set("clockSel",6);
break;
}
switch (oldFlags&0xcc) {
case 0x00:
newFlags.set("chipType",0);
break;
case 0x04:
newFlags.set("chipType",1);
break;
case 0x08:
newFlags.set("chipType",2);
break;
case 0x0c:
newFlags.set("chipType",3);
break;
case 0x40:
newFlags.set("chipType",4);
break;
case 0x44:
newFlags.set("chipType",5);
break;
case 0x48:
newFlags.set("chipType",6);
break;
case 0x4c:
newFlags.set("chipType",7);
break;
case 0x80:
newFlags.set("chipType",8);
break;
case 0x84:
newFlags.set("chipType",9);
break;
}
if (oldFlags&16) newFlags.set("noPhaseReset",true);
break;
case DIV_SYSTEM_GB:
newFlags.set("chipType",(int)(oldFlags&3));
if (oldFlags&8) newFlags.set("noAntiClick",true);
break;
case DIV_SYSTEM_PCE:
newFlags.set("clockSel",(int)(oldFlags&1));
newFlags.set("chipType",(oldFlags&4)?1:0);
if (oldFlags&8) newFlags.set("noAntiClick",true);
break;
case DIV_SYSTEM_NES:
case DIV_SYSTEM_VRC6:
case DIV_SYSTEM_FDS:
case DIV_SYSTEM_MMC5:
case DIV_SYSTEM_SAA1099:
case DIV_SYSTEM_OPZ:
switch (oldFlags) {
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_C64_6581:
case DIV_SYSTEM_C64_8580:
switch (oldFlags&15) {
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_YM2610:
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610B_EXT:
switch (oldFlags&0xff) {
case 0:
newFlags.set("clockSel",0);
break;
case 1:
newFlags.set("clockSel",1);
break;
}
break;
case DIV_SYSTEM_AY8910:
case DIV_SYSTEM_AY8930:
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;
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:
if (sys==DIV_SYSTEM_AY8910) newFlags.set("clockSel",13);
break;
case 14:
if (sys==DIV_SYSTEM_AY8910) newFlags.set("clockSel",14);
break;
}
if (sys==DIV_SYSTEM_AY8910) switch ((oldFlags>>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<unsigned int> 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; i<DIV_MAX_CHIPS; i++) {
unsigned char sysID=reader.readC();
ds.system[i]=systemFromFileFur(sysID);
logD("- %d: %.2x (%s)",i,sysID,getSystemName(ds.system[i]));
if (sysID!=0 && systemToFileFur(ds.system[i])==0) {
logE("unrecognized system ID %.2x",sysID);
lastError=fmt::sprintf("unrecognized system ID %.2x!",sysID);
delete[] file;
return false;
}
if (ds.system[i]!=DIV_SYSTEM_NULL) ds.systemLen=i+1;
}
int tchans=0;
for (int i=0; i<ds.systemLen; i++) {
tchans+=getChannelCount(ds.system[i]);
}
if (tchans>DIV_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; i<DIV_MAX_CHIPS; i++) {
signed char oldSysVol=reader.readC();
ds.systemVol[i]=(float)oldSysVol/64.0f;
if (ds.version<59 && ds.system[i]==DIV_SYSTEM_NES) {
ds.systemVol[i]/=4;
}
}
// system panning
for (int i=0; i<DIV_MAX_CHIPS; i++) {
signed char oldSysPan=reader.readC();
ds.systemPan[i]=(float)oldSysPan/127.0f;
}
// system props
for (int i=0; i<DIV_MAX_CHIPS; i++) {
sysFlagsPtr[i]=reader.readI();
}
// handle compound systems
for (int i=0; i<DIV_MAX_CHIPS; i++) {
if (ds.system[i]==DIV_SYSTEM_GENESIS ||
ds.system[i]==DIV_SYSTEM_GENESIS_EXT ||
ds.system[i]==DIV_SYSTEM_ARCADE) {
for (int j=31; j>i; 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; i<ds.insLen; i++) {
insPtr[i]=reader.readI();
}
for (int i=0; i<ds.waveLen; i++) {
wavePtr[i]=reader.readI();
}
for (int i=0; i<ds.sampleLen; i++) {
samplePtr[i]=reader.readI();
}
patPtr.reserve(numberOfPats);
for (int i=0; i<numberOfPats; i++) patPtr.push_back(reader.readI());
logD("reading orders (%d)...",subSong->ordersLen);
for (int i=0; i<tchans; i++) {
for (int j=0; j<subSong->ordersLen; j++) {
subSong->orders.ord[i][j]=reader.readC();
}
}
for (int i=0; i<tchans; i++) {
subSong->pat[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; i<tchans; i++) {
if (ds.version<189) {
subSong->chanShow[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; i<tchans; i++) {
subSong->chanCollapse[i]=reader.readC();
}
if (ds.version<92) {
for (int i=0; i<tchans; i++) {
if (subSong->chanCollapse[i]>0) subSong->chanCollapse[i]=3;
}
}
for (int i=0; i<tchans; i++) {
subSong->chanName[i]=reader.readString();
}
for (int i=0; i<tchans; i++) {
subSong->chanShortName[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<numberOfSubSongs; i++) {
subSongPtr[i]=reader.readI();
}
}
// additional metadata
if (ds.version>=103) {
ds.systemName=reader.readString();
ds.category=reader.readString();
ds.nameJ=reader.readString();
ds.authorJ=reader.readString();
ds.systemNameJ=reader.readString();
ds.categoryJ=reader.readString();
} else {
ds.systemName=getSongSystemLegacyName(ds,!getConfInt("noMultiSystem",0));
ds.autoSystem=true;
}
// system output config
if (ds.version>=135) {
for (int i=0; i<ds.systemLen; i++) {
ds.systemVol[i]=reader.readF();
ds.systemPan[i]=reader.readF();
ds.systemPanFR[i]=reader.readF();
}
// patchbay
unsigned int conns=reader.readI();
if (conns>0) ds.patchbay.reserve(conns);
for (unsigned int i=0; i<conns; i++) {
ds.patchbay.push_back((unsigned int)reader.readI());
}
}
if (ds.version>=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<grooveCount; i++) {
DivGroovePattern gp;
gp.len=reader.readC();
for (int j=0; j<16; j++) {
gp.val[j]=reader.readC();
}
ds.grooves.push_back(gp);
}
}
if (ds.version>=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<DIV_MAX_CHIPS; i++) {
if (sysFlagsPtr[i]==0) continue;
if (!reader.seek(sysFlagsPtr[i],SEEK_SET)) {
logE("couldn't seek to chip %d flags!",i+1);
lastError=fmt::sprintf("couldn't seek to chip %d flags!",i+1);
ds.unload();
delete[] file;
return false;
}
reader.read(magic,4);
if (strcmp(magic,"FLAG")!=0) {
logE("%d: invalid flag header!",i);
lastError="invalid flag header!";
ds.unload();
delete[] file;
return false;
}
reader.readI();
String data=reader.readString();
ds.systemFlags[i].loadFromMemory(data.c_str());
}
} else {
logD("reading old chip flags...");
for (int i=0; i<ds.systemLen; i++) {
convertOldFlags(sysFlagsPtr[i],ds.systemFlags[i],ds.system[i]);
}
}
// read asset directories
if (ds.version>=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; i<numberOfSubSongs; i++) {
ds.subsong.push_back(new DivSubSong);
if (!reader.seek(subSongPtr[i],SEEK_SET)) {
logE("couldn't seek to subsong %d!",i+1);
lastError=fmt::sprintf("couldn't seek to subsong %d!",i+1);
ds.unload();
delete[] file;
return false;
}
reader.read(magic,4);
if (strcmp(magic,"SONG")!=0) {
logE("%d: invalid subsong header!",i);
lastError="invalid subsong header!";
ds.unload();
delete[] file;
return false;
}
reader.readI();
subSong=ds.subsong[i+1];
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();
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; j<tchans; j++) {
for (int k=0; k<subSong->ordersLen; k++) {
subSong->orders.ord[j][k]=reader.readC();
}
}
for (int i=0; i<tchans; i++) {
subSong->pat[i].effectCols=reader.readC();
}
for (int i=0; i<tchans; i++) {
if (ds.version<189) {
subSong->chanShow[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; i<tchans; i++) {
subSong->chanCollapse[i]=reader.readC();
}
for (int i=0; i<tchans; i++) {
subSong->chanName[i]=reader.readString();
}
for (int i=0; i<tchans; i++) {
subSong->chanShortName[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; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument;
logD("reading instrument %d at %x...",i,insPtr[i]);
if (!reader.seek(insPtr[i],SEEK_SET)) {
logE("couldn't seek to instrument %d!",i);
lastError=fmt::sprintf("couldn't seek to instrument %d!",i);
ds.unload();
delete ins;
delete[] file;
return false;
}
if (ins->readInsData(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; i<ds.waveLen; i++) {
DivWavetable* wave=new DivWavetable;
logD("reading wavetable %d at %x...",i,wavePtr[i]);
if (!reader.seek(wavePtr[i],SEEK_SET)) {
logE("couldn't seek to wavetable %d!",i);
lastError=fmt::sprintf("couldn't seek to wavetable %d!",i);
ds.unload();
delete wave;
delete[] file;
return false;
}
if (wave->readWaveData(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; i<ds.sampleLen; i++) {
DivSample* sample=new DivSample;
if (!reader.seek(samplePtr[i],SEEK_SET)) {
logE("couldn't seek to sample %d!",i);
lastError=fmt::sprintf("couldn't seek to sample %d!",i);
ds.unload();
delete sample;
delete[] file;
return false;
}
if (sample->readSampleData(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; j<ds.subsong[subs]->patLen; 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<<k)) {
pat->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; j<ds.subsong[subs]->patLen; 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; k<ds.subsong[subs]->pat[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()<reader.size()) {
if ((reader.tell()+1)!=reader.size()) {
logW("premature end of song (we are at %x, but size is %x)",reader.tell(),reader.size());
}
}
// convert OPM/NES instrument types
if (ds.version<117) {
int opnCount=0;
int opmCount=0;
int snCount=0;
int nesCount=0;
for (int i=0; i<ds.systemLen; i++) {
switch (ds.system[i]) {
case DIV_SYSTEM_NES:
case DIV_SYSTEM_MMC5:
nesCount++;
break;
case DIV_SYSTEM_SMS:
snCount++;
break;
case DIV_SYSTEM_YM2151:
case DIV_SYSTEM_OPZ:
opmCount++;
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_YM2203:
case DIV_SYSTEM_YM2203_EXT:
case DIV_SYSTEM_YM2608:
case DIV_SYSTEM_YM2608_EXT:
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
opnCount++;
break;
default:
break;
}
}
if (opmCount>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<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_YM2612_EXT ||
ds.system[i]==DIV_SYSTEM_YM2612_DUALPCM_EXT ||
ds.system[i]==DIV_SYSTEM_YM2610_EXT ||
ds.system[i]==DIV_SYSTEM_YM2610_FULL_EXT ||
ds.system[i]==DIV_SYSTEM_YM2610B_EXT ||
ds.system[i]==DIV_SYSTEM_YM2203_EXT ||
ds.system[i]==DIV_SYSTEM_YM2608_EXT ||
ds.system[i]==DIV_SYSTEM_YM2612_CSM ||
ds.system[i]==DIV_SYSTEM_YM2203_CSM ||
ds.system[i]==DIV_SYSTEM_YM2608_CSM ||
ds.system[i]==DIV_SYSTEM_YM2610_CSM ||
ds.system[i]==DIV_SYSTEM_YM2610B_CSM) {
if (ds.version<125) {
ds.systemFlags[i].set("noExtMacros",true);
}
if (ds.version<133) {
ds.systemFlags[i].set("fbAllOps",true);
}
}
}
// SN noise compat
if (ds.version<128) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_SMS ||
ds.system[i]==DIV_SYSTEM_T6W28) {
ds.systemFlags[i].set("noEasyNoise",true);
}
}
}
// OPL3 pan compat
if (ds.version<134) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_OPL3 ||
ds.system[i]==DIV_SYSTEM_OPL3_DRUMS) {
ds.systemFlags[i].set("compatPan",true);
}
}
}
// new YM2612/SN/X1-010 volumes
if (ds.version<137) {
for (int i=0; i<ds.systemLen; i++) {
switch (ds.system[i]) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
case DIV_SYSTEM_YM2612_CSM:
ds.systemVol[i]/=2.0;
break;
case DIV_SYSTEM_SMS:
case DIV_SYSTEM_T6W28:
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
ds.systemVol[i]/=1.5;
break;
case DIV_SYSTEM_X1_010:
ds.systemVol[i]/=4.0;
break;
default:
break;
}
}
}
// Namco C30 noise compat
if (ds.version<145) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_NAMCO_CUS30) {
ds.systemFlags[i].set("newNoise",false);
}
}
}
// SrgaPCM slide compat
if (ds.version<153) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_SEGAPCM || ds.system[i]==DIV_SYSTEM_SEGAPCM_COMPAT) {
ds.systemFlags[i].set("oldSlides",true);
}
}
}
// NES PCM compat
if (ds.version<154) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_NES) {
ds.systemFlags[i].set("dpcmMode",false);
}
}
}
// C64 key priority compat
if (ds.version<160) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_C64_8580 || ds.system[i]==DIV_SYSTEM_C64_6581) {
ds.systemFlags[i].set("keyPriority",false);
}
}
}
// Namco 163 pitch compensation compat
if (ds.version<165) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_N163) {
ds.systemFlags[i].set("lenCompensate",true);
}
}
}
// OPM/OPZ slide compat
if (ds.version<176) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_YM2151 ||
ds.system[i]==DIV_SYSTEM_OPZ) {
ds.systemFlags[i].set("brokenPitch",true);
}
}
}
// C64 1Exy compat
if (ds.version<186) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_C64_8580 || ds.system[i]==DIV_SYSTEM_C64_6581) {
ds.systemFlags[i].set("no1EUpdate",true);
}
}
}
// C64 original reset time and multiply relative
if (ds.version<187) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_C64_8580 || ds.system[i]==DIV_SYSTEM_C64_6581) {
ds.systemFlags[i].set("initResetTime",1);
ds.systemFlags[i].set("multiplyRel",true);
}
}
}
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::loadMod(unsigned char* file, size_t len) {
struct InvalidHeaderException {};
bool success=false;
int chCount=0;
int ordCount=0;
std::vector<int> 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; i<insCount; i++) {
DivSample* sample=new DivSample;
sample->depth=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; j<chCount; j++) {
ds.subsong[0]->orders.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; ch<chCount; ch++) {
for (int i=0; i<5; i++) {
fxUsage[ch][i]=false;
}
}
for (int pat=0; pat<=patMax; pat++) {
DivPattern* chpats[DIV_MAX_CHANS];
for (int ch=0; ch<chCount; ch++) {
chpats[ch]=ds.subsong[0]->pat[ch].getPattern(pat,true);
}
for (int row=0; row<64; row++) {
for (int ch=0; ch<chCount; ch++) {
short* dstrow=chpats[ch]->data[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; i<insCount; i++) {
logV("- %d: %d %d %d",i,pos,ds.sample[i]->samples,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; i<ds.systemLen; i++) {
ds.system[i]=DIV_SYSTEM_AMIGA;
ds.systemFlags[i].set("clockSel",1); // PAL
ds.systemFlags[i].set("stereoSep",80);
ds.systemFlags[i].set("bypassLimits",bypassLimits);
ds.systemFlags[i].set("chipType",(bool)(ds.systemLen>1 || bypassLimits));
}
for(int i=0; i<chCount; i++) {
ds.subsong[0]->chanShow[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; i<ds.systemLen*4; i++) {
ds.subsong[0]->pat[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; i<insCount; i++) {
DivInstrument* ins=new DivInstrument;
ins->type=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)<i)?0x87:0x00));
}
break;
case 0x10: case 0x11: case 0x12: case 0x13:
case 0x14: case 0x15: case 0x16: case 0x17:
case 0x18: case 0x19: case 0x1a: case 0x1b:
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
// pulse
for (int i=0; i<32; i++) {
wave->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<ordersLen; i++) {
ord[i]=reader.readC();
logV("- %.2x",ord[i]);
}
// should be even
if (ordersLen&1) reader.readC();
logD("reading ins pointers...");
for (int i=0; i<ds.insLen; i++) {
insPtr[i]=reader.readS();
logV("- %.2x",insPtr[i]);
}
logD("reading pat pointers...");
for (int i=0; i<patCount; i++) {
patPtr[i]=reader.readS();
logV("- %.2x",patPtr[i]);
}
if (defaultPan) {
reader.read(chanPan,16);
} else {
memset(chanPan,0,16);
}
// determine chips to use
ds.systemLen=0;
bool hasPCM=false;
bool hasFM=false;
for (int i=0; i<32; i++) {
if (!(chanSettings[i]&128)) continue;
if ((chanSettings[i]&127)>=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; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument;
if (!reader.seek(0x4c+insPtr[i]*16,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete ins;
delete[] file;
return false;
}
reader.read(magic,4);
if (memcmp(magic,"SCRS",4)==0) {
ins->type=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<FCSequence> seq;
struct FCPattern {
unsigned char note[32];
unsigned char val[32];
};
std::vector<FCPattern> pat;
struct FCMacro {
unsigned char val[64];
};
std::vector<FCMacro> freqMacros;
std::vector<FCMacro> 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; i<seqLen; i++) {
FCSequence s;
for (int j=0; j<4; j++) {
s.pat[j]=reader.readC();
s.transpose[j]=reader.readC();
s.offsetIns[j]=reader.readC();
}
s.speed=reader.readC();
seq.push_back(s);
logV(
"%.2x | %.2x%.2x%.2x %.2x%.2x%.2x %.2x%.2x%.2x %.2x%.2x%.2x | %.2x",
i,
s.pat[0],s.transpose[0],s.offsetIns[0],
s.pat[1],s.transpose[1],s.offsetIns[1],
s.pat[2],s.transpose[2],s.offsetIns[2],
s.pat[3],s.transpose[3],s.offsetIns[3],
s.speed
);
}
// patterns
if (!reader.seek(patPtr,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete[] file;
return false;
}
patLen/=64;
logD("reading patterns... (%d)",patLen);
pat.reserve(patLen);
for (unsigned int i=0; i<patLen; i++) {
FCPattern p;
logV("- pattern %d",i);
for (int j=0; j<32; j++) {
p.note[j]=reader.readC();
p.val[j]=reader.readC();
//logV("%.2x | %.2x %.2x",j,p.note[j],p.val[j]);
}
pat.push_back(p);
}
// freq sequences
if (!reader.seek(freqMacroPtr,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete[] file;
return false;
}
freqMacroLen/=64;
logD("reading freq sequences... (%d)",freqMacroLen);
freqMacros.reserve(freqMacroLen);
for (unsigned int i=0; i<freqMacroLen; i++) {
FCMacro m;
reader.read(m.val,64);
freqMacros.push_back(m);
}
// vol sequences
if (!reader.seek(volMacroPtr,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete[] file;
return false;
}
volMacroLen/=64;
logD("reading volume sequences... (%d)",volMacroLen);
volMacros.reserve(volMacroLen);
for (unsigned int i=0; i<volMacroLen; i++) {
FCMacro m;
reader.read(m.val,64);
volMacros.push_back(m);
}
// samples
if (!reader.seek(samplePtr,SEEK_SET)) {
logE("premature end of file!");
lastError="incomplete file";
delete[] file;
return false;
}
logD("reading samples...");
ds.sample.reserve(10);
for (int i=0; i<10; i++) {
DivSample* s=new DivSample;
s->depth=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; i<howMany; i++) {
w->data[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; i<seqLen; i++) {
for (int j=0; j<4; j++) {
ds.subsong[0]->orders.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; i<volMacroLen; i++) {
DivInstrument* ins=new DivInstrument;
FCMacro& m=volMacros[i];
ins->type=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 <?
for (int k=0; k<=susTime; k++) {
ins->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 <?
for (int k=0; k<=slideTime; k++) {
if (slideStep>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; k<MAX(1,seqSpeed); k++) {
ins->std.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 (freqMacro<freqMacros.size()) {
FCMacro& fm=freqMacros[freqMacro];
for (int j=0; j<64; j++) {
loopMapFreq[j]=ins->std.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)<ds.wave.size()) {
ins->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 (vibPos<vibDepth);
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 (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; i<ds.systemLen; i++) {
calcChans+=getChannelCount(ds.system[i]);
}
if (calcChans!=tchans) {
logE("channel counts do not match! %d != %d",tchans,calcChans);
lastError="channel counts do not match";
delete[] file;
return false;
}
if (tchans>DIV_MAX_CHANS) {
tchans=DIV_MAX_CHANS;
logW("too many channels!");
}
} 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; i<tchans; i++) {
// TODO: obey channel ID
unsigned char chID=reader.readC();
logV("for channel ID %d",chID);
for (int j=0; j<=totalSongs; j++) {
unsigned char effectCols=reader.readC();
ds.subsong[j]->pat[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.insLen; i++) {
DivInstrument* ins=new DivInstrument;
ds.ins.push_back(ins);
}
logV("instruments:");
for (int i=0; i<ds.insLen; i++) {
unsigned int insIndex=reader.readI();
if (insIndex>=ds.ins.size()) {
logE("instrument index %d is out of range!",insIndex);
lastError="instrument index out of range";
delete[] file;
return false;
}
DivInstrument* ins=ds.ins[insIndex];
unsigned char insType=reader.readC();
switch (insType) {
case 1:
ins->type=DIV_INS_NES;
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<totalSeqs; j++) {
hasSequence[insIndex][j]=reader.readC();
sequenceIndex[insIndex][j]=reader.readC();
}
const int dpcmNotes=(blockVersion>=2)?96:72;
for (int j=0; j<dpcmNotes; j++) {
ins->amiga.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; j<totalSeqs; j++) {
hasSequence[insIndex][j]=reader.readC();
sequenceIndex[insIndex][j]=reader.readC();
}
break;
}
case DIV_INS_OPLL: {
ins->fm.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; j<ins->std.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; j<ins->std.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; j<ins->std.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; i<ds.subsong.size(); i++) {
DivSubSong* s=ds.subsong[i];
s->ordersLen=reader.readI();
if (blockVersion>=3) {
s->speeds.val[0]=reader.readI();
}
if (blockVersion>=2) {
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; j<s->ordersLen; j++) {
for (int k=0; k<why; k++) {
unsigned char o=reader.readC();
logV("%.2x",o);
s->orders.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()<blockEnd) {
int subs=0;
if (blockVersion>=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<numRows; i++) {
unsigned int row=0;
if (blockVersion>=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; j<effectCols; j++) {
unsigned char nextEffect=reader.readC();
unsigned char nextEffectVal=0;
if (nextEffect!=0 || blockVersion<6) nextEffectVal=reader.readC();
if (nextEffect==0 && nextEffectVal==0) {
pat->data[row][4+(j*2)]=-1;
pat->data[row][5+(j*2)]=-1;
} else {
if (nextEffect<ftEffectMapSize) {
pat->data[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<InflateBlock*> 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<DivAssetDir>& 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<DivAssetDir>& 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<numDirs; i++) {
DivAssetDir d;
d.name=reader.readString();
unsigned short numEntries=reader.readS();
d.entries.reserve(numEntries);
for (unsigned short j=0; j<numEntries; j++) {
d.entries.push_back(((unsigned char)reader.readC()));
}
dir.push_back(d);
}
return DIV_DATA_SUCCESS;
}
SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) {
saveLock.lock();
std::vector<int> subSongPtr;
std::vector<int> sysFlagsPtr;
std::vector<int> insPtr;
std::vector<int> wavePtr;
std::vector<int> samplePtr;
std::vector<int> 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<PatToWrite> patsToWrite;
if (getConfInt("saveUnusedPatterns",0)==1) {
for (int i=0; i<chans; i++) {
for (size_t j=0; j<song.subsong.size(); j++) {
DivSubSong* subs=song.subsong[j];
for (int k=0; k<DIV_MAX_PATTERNS; k++) {
if (subs->pat[i].data[k]==NULL) continue;
patsToWrite.push_back(PatToWrite(j,i,k));
}
}
}
} else {
bool alreadyAdded[DIV_MAX_PATTERNS];
for (int i=0; i<chans; i++) {
for (size_t j=0; j<song.subsong.size(); j++) {
DivSubSong* subs=song.subsong[j];
memset(alreadyAdded,0,DIV_MAX_PATTERNS*sizeof(bool));
for (int k=0; k<subs->ordersLen; 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<DIV_MAX_CHIPS; i++) {
if (i>=song.systemLen) {
w->writeC(0);
} else {
w->writeC(systemToFileFur(song.system[i]));
}
}
for (int i=0; i<DIV_MAX_CHIPS; i++) {
w->writeC(song.systemVol[i]*64.0f);
}
for (int i=0; i<DIV_MAX_CHIPS; i++) {
w->writeC(song.systemPan[i]*127.0f);
}
// chip flags (we'll seek here later)
sysFlagsPtrSeek=w->tell();
for (int i=0; i<DIV_MAX_CHIPS; i++) {
w->writeI(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; i<song.insLen; i++) {
w->writeI(0);
}
// wavetable pointers (we'll seek here later)
for (int i=0; i<song.waveLen; i++) {
w->writeI(0);
}
// sample pointers (we'll seek here later)
for (int i=0; i<song.sampleLen; i++) {
w->writeI(0);
}
// pattern pointers (we'll seek here later)
for (size_t i=0; i<patsToWrite.size(); i++) {
w->writeI(0);
}
for (int i=0; i<chans; i++) {
for (int j=0; j<subSong->ordersLen; j++) {
w->writeC(subSong->orders.ord[i][j]);
}
}
for (int i=0; i<chans; i++) {
w->writeC(subSong->pat[i].effectCols);
}
for (int i=0; i<chans; i++) {
w->writeC(
(subSong->chanShow[i]?1:0)|
(subSong->chanShowChanOsc[i]?2:0)
);
}
for (int i=0; i<chans; i++) {
w->writeC(subSong->chanCollapse[i]);
}
for (int i=0; i<chans; i++) {
w->writeString(subSong->chanName[i],false);
}
for (int i=0; i<chans; i++) {
w->writeString(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; i<song.systemLen; i++) {
w->writeF(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; subSongIndex<song.subsong.size(); subSongIndex++) {
subSong=song.subsong[subSongIndex];
subSongPtr.push_back(w->tell());
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; i<chans; i++) {
for (int j=0; j<subSong->ordersLen; j++) {
w->writeC(subSong->orders.ord[i][j]);
}
}
for (int i=0; i<chans; i++) {
w->writeC(subSong->pat[i].effectCols);
}
for (int i=0; i<chans; i++) {
w->writeC(
(subSong->chanShow[i]?1:0)|
(subSong->chanShowChanOsc[i]?2:0)
);
}
for (int i=0; i<chans; i++) {
w->writeC(subSong->chanCollapse[i]);
}
for (int i=0; i<chans; i++) {
w->writeString(subSong->chanName[i],false);
}
for (int i=0; i<chans; i++) {
w->writeString(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; i<song.systemLen; i++) {
String data=song.systemFlags[i].toString();
if (data.empty()) {
sysFlagsPtr.push_back(0);
continue;
}
sysFlagsPtr.push_back(w->tell());
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; i<song.insLen; i++) {
DivInstrument* ins=song.ins[i];
insPtr.push_back(w->tell());
ins->putInsData2(w,false);
}
/// WAVETABLE
wavePtr.reserve(song.waveLen);
for (int i=0; i<song.waveLen; i++) {
DivWavetable* wave=song.wave[i];
wavePtr.push_back(w->tell());
wave->putWaveData(w);
}
/// SAMPLE
samplePtr.reserve(song.sampleLen);
for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
samplePtr.push_back(w->tell());
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; j<song.subsong[i.subsong]->patLen; 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; k<song.subsong[i.subsong]->pat[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<<k);
if (pat->data[j][5+k]!=-1) effectMask|=(2<<k);
}
if (mask==0) {
emptyRows++;
if (emptyRows>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; j<song.subsong[i.subsong]->patLen; 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; k<song.subsong[i.subsong]->pat[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; i<song.insLen; i++) {
w->writeI(insPtr[i]);
}
// wavetable pointers
for (int i=0; i<song.waveLen; i++) {
w->writeI(wavePtr[i]);
}
// sample pointers
for (int i=0; i<song.sampleLen; i++) {
w->writeI(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; i<sysFlagsPtr.size(); i++) {
w->writeI(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; i<chans; i++) {
for (int j=0; j<curSubSong->ordersLen; 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; i<chans; i++) {
for (int j=0; j<curSubSong->ordersLen; 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; j<realVolMacroLen; j++) {
w->writeI((-i->std.algMacro.val[j])+18);
}
} else {
w->writeC(realVolMacroLen);
for (int j=0; j<realVolMacroLen; j++) {
w->writeI(i->std.volMacro.val[j]);
}
}
} else {
w->writeC(realVolMacroLen);
for (int j=0; j<realVolMacroLen; j++) {
w->writeI(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; j<i->std.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; j<realArpMacroLen; j++) {
if ((i->std.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; j<realArpMacroLen; j++) {
if ((i->std.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; j<realDutyMacroLen; j++) {
w->writeI(i->std.dutyMacro.val[j]+12);
}
} else {
for (int j=0; j<realDutyMacroLen; j++) {
w->writeI(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; j<realWaveMacroLen; j++) {
w->writeI(i->std.waveMacro.val[j]);
}
if (realWaveMacroLen>0) {
w->writeC(i->std.waveMacro.loop);
}
if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) {
w->writeC(i->c64.triOn);
w->writeC(i->c64.sawOn);
w->writeC(i->c64.pulseOn);
w->writeC(i->c64.noiseOn);
w->writeC(i->c64.a);
w->writeC(i->c64.d);
w->writeC(i->c64.s);
w->writeC(i->c64.r);
logW("duty and cutoff precision will be lost!");
w->writeC((i->c64.duty*100)/4095);
w->writeC(i->c64.ringMod);
w->writeC(i->c64.oscSync);
w->writeC(i->c64.toFilter);
w->writeC(volIsCutoff);
w->writeC(i->c64.initFilter);
w->writeC(i->c64.res);
w->writeC((i->c64.cut*100)/2047);
w->writeC(i->c64.hp);
w->writeC(i->c64.bp);
w->writeC(i->c64.lp);
w->writeC(i->c64.ch3off);
}
if (sys==DIV_SYSTEM_GB) {
w->writeC(i->gb.envVol);
w->writeC(i->gb.envDir);
w->writeC(i->gb.envLen);
w->writeC(i->gb.soundLen);
}
}
}
w->writeC(song.wave.size());
for (DivWavetable* i: song.wave) {
w->writeI(i->len);
if (sys==DIV_SYSTEM_NES_FDS && version<26) {
for (int j=0; j<i->len; j++) {
w->writeI(i->data[j]>>2);
}
} else {
for (int j=0; j<i->len; j++) {
w->writeI(i->data[j]);
}
}
}
bool relWarning=false;
for (int i=0; i<getChannelCount(sys); i++) {
w->writeC(curPat[i].effectCols);
for (int j=0; j<curSubSong->ordersLen; j++) {
DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false);
for (int k=0; k<curSubSong->patLen; 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; l<curPat[i].effectCols*2; l++) {
w->writeS(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; j<i->length16; 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; i<len; i++) {
if (i==m.loop) {
w->writeText(" |");
}
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; i<song.systemLen; i++) {
w->writeText(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; i<song.insLen; i++) {
DivInstrument* ins=song.ins[i];
w->writeText(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; j<ins->fm.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; j<ins->fm.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; j<ins->gb.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; i<song.waveLen; i++) {
DivWavetable* wave=song.wave[i];
w->writeText(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; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeText(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; i<bufLen; i++) {
if ((i&15)==0) w->writeText(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; i<song.subsong.size(); i++) {
DivSubSong* s=song.subsong[i];
w->writeText(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; j<s->speeds.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; j<s->ordersLen; j++) {
w->writeText(fmt::sprintf("%.2X |",j));
for (int k=0; k<chans; k++) {
w->writeText(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; j<s->ordersLen; j++) {
w->writeText(fmt::sprintf("----- ORDER %.2X\n",j));
for (int k=0; k<s->patLen; k++) {
w->writeText(fmt::sprintf("%.2X ",k));
for (int l=0; l<chans; l++) {
DivPattern* p=s->pat[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; m<s->pat[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;
}