#include "engine.h" #include "instrument.h" #include "safeReader.h" #include "../ta-log.h" #include "../audio/sdl.h" #ifdef HAVE_JACK #include "../audio/jack.h" #endif #include "platform/genesis.h" #include "platform/genesisext.h" #include "platform/sms.h" #include "platform/gb.h" #include "platform/pce.h" #include "platform/nes.h" #include "platform/c64.h" #include "platform/arcade.h" #include "platform/ym2610.h" #include "platform/dummy.h" #include #include #include void process(void* u, float** in, float** out, int inChans, int outChans, unsigned int size) { ((DivEngine*)u)->nextBuf(in,out,inChans,outChans,size); } #define DIV_READ_SIZE 131072 #define DIV_DMF_MAGIC ".DelekDefleMask." 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; } }; DivSystem systemFromFile(unsigned char val) { switch (val) { case 0x01: return DIV_SYSTEM_YMU759; case 0x02: return DIV_SYSTEM_GENESIS; case 0x03: return DIV_SYSTEM_SMS; case 0x04: return DIV_SYSTEM_GB; case 0x05: return DIV_SYSTEM_PCE; case 0x06: return DIV_SYSTEM_NES; case 0x07: return DIV_SYSTEM_C64_8580; case 0x08: return DIV_SYSTEM_ARCADE; case 0x09: return DIV_SYSTEM_YM2610; case 0x42: return DIV_SYSTEM_GENESIS_EXT; case 0x47: return DIV_SYSTEM_C64_6581; case 0x49: return DIV_SYSTEM_YM2610_EXT; } return DIV_SYSTEM_NULL; } unsigned char systemToFile(DivSystem val) { switch (val) { case DIV_SYSTEM_YMU759: return 0x01; case DIV_SYSTEM_GENESIS: return 0x02; case DIV_SYSTEM_SMS: return 0x03; case DIV_SYSTEM_GB: return 0x04; case DIV_SYSTEM_PCE: return 0x05; case DIV_SYSTEM_NES: return 0x06; case DIV_SYSTEM_C64_8580: return 0x07; case DIV_SYSTEM_ARCADE: return 0x08; case DIV_SYSTEM_YM2610: return 0x09; case DIV_SYSTEM_GENESIS_EXT: return 0x42; case DIV_SYSTEM_C64_6581: return 0x47; case DIV_SYSTEM_YM2610_EXT: return 0x49; case DIV_SYSTEM_NULL: return 0; } return 0; } int DivEngine::getChannelCount(DivSystem sys) { switch (sys) { case DIV_SYSTEM_NULL: return 0; case DIV_SYSTEM_YMU759: return 17; case DIV_SYSTEM_GENESIS: return 10; case DIV_SYSTEM_SMS: case DIV_SYSTEM_GB: return 4; case DIV_SYSTEM_PCE: return 6; case DIV_SYSTEM_NES: return 5; case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: return 3; case DIV_SYSTEM_ARCADE: case DIV_SYSTEM_GENESIS_EXT: case DIV_SYSTEM_YM2610: return 13; case DIV_SYSTEM_YM2610_EXT: return 16; } return 0; } bool DivEngine::isFMSystem(DivSystem sys) { return (sys==DIV_SYSTEM_GENESIS || sys==DIV_SYSTEM_GENESIS_EXT || sys==DIV_SYSTEM_ARCADE || sys==DIV_SYSTEM_YM2610 || sys==DIV_SYSTEM_YM2610_EXT || sys==DIV_SYSTEM_YMU759); } const char* chanShortNames[11][17]={ {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759 {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3) {"S1", "S2", "S3", "NO"}, // SMS {"S1", "S2", "WA", "NO"}, // GB {"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"}, // PCE {"S1", "S2", "TR", "NO", "PCM"}, // NES {"CH1", "CH2", "CH3"}, // C64 {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "P1", "P2", "P3", "P4", "P5"}, // Arcade {"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"}, // YM2610 {"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"}, // YM2610 (extended channel 2) }; const char* DivEngine::getChannelShortName(int chan) { switch (song.system) { case DIV_SYSTEM_NULL: case DIV_SYSTEM_YMU759: return chanShortNames[0][chan]; break; case DIV_SYSTEM_GENESIS: return chanShortNames[1][chan]; break; case DIV_SYSTEM_GENESIS_EXT: return chanShortNames[2][chan]; break; case DIV_SYSTEM_SMS: return chanShortNames[3][chan]; break; case DIV_SYSTEM_GB: return chanShortNames[4][chan]; break; case DIV_SYSTEM_PCE: return chanShortNames[5][chan]; break; case DIV_SYSTEM_NES: return chanShortNames[6][chan]; break; case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: return chanShortNames[7][chan]; break; case DIV_SYSTEM_ARCADE: return chanShortNames[8][chan]; break; case DIV_SYSTEM_YM2610: return chanShortNames[9][chan]; break; case DIV_SYSTEM_YM2610_EXT: return chanShortNames[10][chan]; break; } return "??"; } int DivEngine::getMaxVolume() { switch (song.system) { case DIV_SYSTEM_PCE: return 31; default: return 15; } return 127; } int DivEngine::getMaxDuty() { switch (song.system) { case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: return 31; case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: return 8; default: return 3; } return 3; } int DivEngine::getMaxWave() { switch (song.system) { case DIV_SYSTEM_PCE: case DIV_SYSTEM_GB: return 31; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: return 7; case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: return 8; default: return 1; } return 1; } bool DivEngine::load(void* f, size_t slen) { unsigned char* file; size_t len; if (slen<16) { logE("too small!"); return false; } if (memcmp(f,DIV_DMF_MAGIC,16)!=0) { logD("loading as zlib...\n"); // try zlib 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) { logE("zlib error: unknown! %d\n",nextErr); } else { logE("zlib error: %s\n",zl.msg); } return false; } std::vector blocks; while (true) { InflateBlock* ib=new InflateBlock(DIV_READ_SIZE); zl.next_out=ib->buf; zl.avail_out=ib->len; nextErr=inflate(&zl,Z_SYNC_FLUSH); if (nextErr!=Z_OK && nextErr!=Z_STREAM_END) { if (zl.msg==NULL) { logE("zlib error: unknown error! %d\n",nextErr); } else { logE("zlib inflate: %s\n",zl.msg); } for (InflateBlock* i: blocks) delete i; blocks.clear(); delete ib; return false; } 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) { logE("zlib end error: unknown error! %d\n",nextErr); } else { logE("zlib end: %s\n",zl.msg); } for (InflateBlock* i: blocks) delete i; blocks.clear(); return false; } size_t finalSize=0; size_t curSeek=0; for (InflateBlock* i: blocks) { finalSize+=i->blockSize; } if (finalSize<1) { logE("compressed too small!\n"); for (InflateBlock* i: blocks) delete i; blocks.clear(); return false; } 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; } else { logD("loading as uncompressed\n"); file=(unsigned char*)f; len=slen; } if (memcmp(file,DIV_DMF_MAGIC,16)!=0) { logE("not a valid module!\n"); return false; } SafeReader reader=SafeReader(file,len); try { DivSong ds; ds.nullWave.len=32; for (int i=0; i<32; i++) { ds.nullWave.data[i]=15; } if (!reader.seek(16,SEEK_SET)) { logE("premature end of file!"); return false; } ds.version=reader.readC(); logI("module version %d (0x%.2x)\n",ds.version,ds.version); unsigned char sys=0; if (ds.version<0x09) { // V E R S I O N -> 3 <- // AWESOME ds.system=DIV_SYSTEM_YMU759; } else { sys=reader.readC(); ds.system=systemFromFile(sys); } if (ds.system==DIV_SYSTEM_NULL) { logE("invalid system 0x%.2x!",sys); return false; } if (ds.system==DIV_SYSTEM_YMU759 && ds.version<0x10) { // TODO 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\n",ds.name.c_str(),ds.author.c_str()); logI("has YMU-specific data:\n"); logI("- carrier: %s\n",ds.carrier.c_str()); logI("- category: %s\n",ds.category.c_str()); logI("- vendor: %s\n",ds.vendor.c_str()); logI("- writer: %s\n",ds.writer.c_str()); logI("- composer: %s\n",ds.composer.c_str()); logI("- arranger: %s\n",ds.arranger.c_str()); logI("- copyright: %s\n",ds.copyright.c_str()); logI("- management group: %s\n",ds.manGroup.c_str()); logI("- management info: %s\n",ds.manInfo.c_str()); logI("- created on: %s\n",ds.createdDate.c_str()); logI("- revision date: %s\n",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\n",ds.name.c_str(),ds.author.c_str()); } logI("reading module data...\n"); if (ds.version>0x0c) { ds.hilightA=reader.readC(); ds.hilightB=reader.readC(); } ds.timeBase=reader.readC(); ds.speed1=reader.readC(); if (ds.version>0x03) { ds.speed2=reader.readC(); ds.pal=reader.readC(); ds.customTempo=reader.readC(); } else { ds.speed2=ds.speed1; } if (ds.version>0x0a) { String hz=reader.readString(3); if (ds.customTempo) { ds.hz=std::stoi(hz); } } // TODO if (ds.version>0x17) { ds.patLen=reader.readI(); } else { ds.patLen=(unsigned char)reader.readC(); } ds.ordersLen=(unsigned char)reader.readC(); if (ds.version<20 && ds.version>3) { ds.arpLen=reader.readC(); } else { ds.arpLen=1; } logI("reading pattern matrix (%d)...\n",ds.ordersLen); for (int i=0; i0x03) { ds.insLen=reader.readC(); } else { ds.insLen=16; } logI("reading instruments (%d)...\n",ds.insLen); for (int i=0; i0x03) { ins->name=reader.readString((unsigned char)reader.readC()); } logD("%d name: %s\n",i,ins->name.c_str()); if (ds.version<0x0b) { // instruments in ancient versions were all FM or STD. ins->mode=1; } else { ins->mode=reader.readC(); } if (ins->mode) { // FM if (!isFMSystem(ds.system)) { logE("FM instrument in non-FM system. oopsie?\n"); return false; } 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!=DIV_SYSTEM_YMU759) ins->fm.ops=4; } else { ins->fm.ops=4; } if (ins->fm.ops!=2 && ins->fm.ops!=4) { logE("invalid op count %d. did we read it wrong?\n",ins->fm.ops); return false; } ins->fm.ams=reader.readC(); for (int j=0; jfm.ops; j++) { ins->fm.op[j].am=reader.readC(); ins->fm.op[j].ar=reader.readC(); if (ds.version<0x13) { ins->fm.op[j].dam=reader.readC(); } ins->fm.op[j].dr=reader.readC(); 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) { // TODO: 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 { ins->fm.op[j].dt2=reader.readC(); } if (ds.version>0x03) { 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(); } 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\n",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 ); } } else { // STD if (ds.system!=DIV_SYSTEM_GB || ds.version<0x12) { ins->std.volMacroLen=reader.readC(); for (int j=0; jstd.volMacroLen; j++) { if (ds.version<0x0e) { ins->std.volMacro[j]=reader.readC(); } else { ins->std.volMacro[j]=reader.readI(); } } if (ins->std.volMacroLen>0) { ins->std.volMacroLoop=reader.readC(); } } ins->std.arpMacroLen=reader.readC(); for (int j=0; jstd.arpMacroLen; j++) { if (ds.version<0x0e) { ins->std.arpMacro[j]=reader.readC(); } else { ins->std.arpMacro[j]=reader.readI(); } } if (ins->std.arpMacroLen>0) { ins->std.arpMacroLoop=reader.readC(); } if (ds.version>0x0f) { // TODO ins->std.arpMacroMode=reader.readC(); } ins->std.dutyMacroLen=reader.readC(); for (int j=0; jstd.dutyMacroLen; j++) { if (ds.version<0x0e) { ins->std.dutyMacro[j]=reader.readC(); } else { ins->std.dutyMacro[j]=reader.readI(); } } if (ins->std.dutyMacroLen>0) { ins->std.dutyMacroLoop=reader.readC(); } ins->std.waveMacroLen=reader.readC(); for (int j=0; jstd.waveMacroLen; j++) { if (ds.version<0x0e) { ins->std.waveMacro[j]=reader.readC(); } else { ins->std.waveMacro[j]=reader.readI(); } } if (ins->std.waveMacroLen>0) { ins->std.waveMacroLoop=reader.readC(); } if (ds.system==DIV_SYSTEM_C64_6581 || ds.system==DIV_SYSTEM_C64_8580) { ins->c64.triOn=reader.readC(); ins->c64.sawOn=reader.readC(); ins->c64.pulseOn=reader.readC(); ins->c64.noiseOn=reader.readC(); ins->c64.a=reader.readC(); ins->c64.d=reader.readC(); ins->c64.s=reader.readC(); ins->c64.r=reader.readC(); ins->c64.duty=reader.readC(); ins->c64.ringMod=reader.readC(); ins->c64.oscSync=reader.readC(); ins->c64.toFilter=reader.readC(); if (ds.version<0x11) { ins->c64.volIsCutoff=reader.readI(); } else { ins->c64.volIsCutoff=reader.readC(); } ins->c64.initFilter=reader.readC(); ins->c64.res=reader.readC(); ins->c64.cut=reader.readC(); ins->c64.hp=reader.readC(); ins->c64.bp=reader.readC(); ins->c64.lp=reader.readC(); ins->c64.ch3off=reader.readC(); } if (ds.system==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(); logD("GB data: vol %d dir %d len %d sl %d\n",ins->gb.envVol,ins->gb.envDir,ins->gb.envLen,ins->gb.soundLen); } } ds.ins.push_back(ins); } if (ds.version>0x0b) { ds.waveLen=(unsigned char)reader.readC(); logI("reading wavetables (%d)...\n",ds.waveLen); for (int i=0; ilen=(unsigned char)reader.readI(); if (wave->len>32) { logE("invalid wave length %d. are we doing something wrong?\n",wave->len); return false; } logD("%d length %d\n",i,wave->len); for (int j=0; jlen; j++) { if (ds.version<0x0e) { wave->data[j]=reader.readC(); } else { wave->data[j]=reader.readI(); } } ds.wave.push_back(wave); } } logI("reading patterns (%d channels, %d orders)...\n",getChannelCount(ds.system),ds.ordersLen); for (int i=0; ieffectRows=1; } else { chan->effectRows=reader.readC(); } logD("%d fx rows: %d\n",i,chan->effectRows); if (chan->effectRows>4 || chan->effectRows<1) { logE("invalid effect row count %d. are you sure everything is ok?\n",chan->effectRows); return false; } for (int j=0; jgetPattern(ds.orders.ord[i][j],true); for (int k=0; kdata[k][0]=reader.readS(); // octave pat->data[k][1]=reader.readS(); if (ds.system==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==DIV_SYSTEM_GENESIS && ds.version<0x0e && pat->data[k][1]>0 && i>5) { // ditto 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; } } for (int l=0; leffectRows; l++) { // effect pat->data[k][4+(l<<1)]=reader.readS(); pat->data[k][5+(l<<1)]=reader.readS(); } // instrument pat->data[k][2]=reader.readS(); } } ds.pat.push_back(chan); } ds.sampleLen=reader.readC(); logI("reading samples (%d)...\n",ds.sampleLen); if (ds.version<0x0b && ds.sampleLen>0) { // TODO what is this for? reader.readC(); } for (int i=0; ilength=reader.readI(); if (sample->length<0) { logE("invalid sample length %d. are we doing something wrong?\n",sample->length); return false; } if (ds.version>0x16) { sample->name=reader.readString((unsigned char)reader.readC()); } else { sample->name=""; } logD("%d name %s (%d)\n",i,sample->name.c_str(),sample->length); if (ds.version<0x0b) { sample->rate=4; sample->pitch=0; sample->vol=0; } else { sample->rate=reader.readC(); sample->pitch=reader.readC(); sample->vol=reader.readC(); } if (ds.version>0x15) { sample->depth=reader.readC(); } else { sample->depth=16; } if (sample->length>0) { if (ds.version<0x0b) { sample->data=new short[1+(sample->length/2)]; reader.read(sample->data,sample->length); sample->length/=2; } else { sample->data=new short[sample->length]; reader.read(sample->data,sample->length*2); } } ds.sample.push_back(sample); } if (reader.tell()name.size(),f)); ERR_CHECK(fwrite(i->name.c_str(),1,i->name.size(),f)); ERR_CHECK(fputc(i->mode,f)); if (i->mode) { // FM ERR_CHECK(fputc(i->fm.alg,f)); ERR_CHECK(fputc(i->fm.fb,f)); ERR_CHECK(fputc(i->fm.fms,f)); ERR_CHECK(fputc(i->fm.ams,f)); for (int j=0; j<4; j++) { DivInstrumentFM::Operator& op=i->fm.op[j]; ERR_CHECK(fputc(op.am,f)); ERR_CHECK(fputc(op.ar,f)); ERR_CHECK(fputc(op.dr,f)); ERR_CHECK(fputc(op.mult,f)); ERR_CHECK(fputc(op.rr,f)); ERR_CHECK(fputc(op.sl,f)); ERR_CHECK(fputc(op.tl,f)); ERR_CHECK(fputc(op.dt2,f)); ERR_CHECK(fputc(op.rs,f)); ERR_CHECK(fputc(op.dt,f)); ERR_CHECK(fputc(op.d2r,f)); ERR_CHECK(fputc(op.ssgEnv,f)); } } else { // STD if (song.system!=DIV_SYSTEM_GB) { ERR_CHECK(fputc(i->std.volMacroLen,f)); ERR_CHECK(fwrite(i->std.volMacro,4,i->std.volMacroLen,f)); if (i->std.volMacroLen>0) { ERR_CHECK(fputc(i->std.volMacroLoop,f)); } } ERR_CHECK(fputc(i->std.arpMacroLen,f)); ERR_CHECK(fwrite(i->std.arpMacro,4,i->std.arpMacroLen,f)); if (i->std.arpMacroLen>0) { ERR_CHECK(fputc(i->std.arpMacroLoop,f)); } ERR_CHECK(fputc(i->std.arpMacroMode,f)); ERR_CHECK(fputc(i->std.dutyMacroLen,f)); ERR_CHECK(fwrite(i->std.dutyMacro,4,i->std.dutyMacroLen,f)); if (i->std.dutyMacroLen>0) { ERR_CHECK(fputc(i->std.dutyMacroLoop,f)); } ERR_CHECK(fputc(i->std.waveMacroLen,f)); ERR_CHECK(fwrite(i->std.waveMacro,4,i->std.waveMacroLen,f)); if (i->std.waveMacroLen>0) { ERR_CHECK(fputc(i->std.waveMacroLoop,f)); } if (song.system==DIV_SYSTEM_C64_6581 || song.system==DIV_SYSTEM_C64_8580) { ERR_CHECK(fputc(i->c64.triOn,f)); ERR_CHECK(fputc(i->c64.sawOn,f)); ERR_CHECK(fputc(i->c64.pulseOn,f)); ERR_CHECK(fputc(i->c64.noiseOn,f)); ERR_CHECK(fputc(i->c64.a,f)); ERR_CHECK(fputc(i->c64.d,f)); ERR_CHECK(fputc(i->c64.s,f)); ERR_CHECK(fputc(i->c64.r,f)); ERR_CHECK(fputc(i->c64.duty,f)); ERR_CHECK(fputc(i->c64.ringMod,f)); ERR_CHECK(fputc(i->c64.oscSync,f)); ERR_CHECK(fputc(i->c64.toFilter,f)); ERR_CHECK(fputc(i->c64.volIsCutoff,f)); ERR_CHECK(fputc(i->c64.initFilter,f)); ERR_CHECK(fputc(i->c64.res,f)); ERR_CHECK(fputc(i->c64.cut,f)); ERR_CHECK(fputc(i->c64.hp,f)); ERR_CHECK(fputc(i->c64.bp,f)); ERR_CHECK(fputc(i->c64.lp,f)); ERR_CHECK(fputc(i->c64.ch3off,f)); } if (song.system==DIV_SYSTEM_GB) { ERR_CHECK(fputc(i->gb.envVol,f)); ERR_CHECK(fputc(i->gb.envDir,f)); ERR_CHECK(fputc(i->gb.envLen,f)); ERR_CHECK(fputc(i->gb.soundLen,f)); } } } ERR_CHECK(fputc(song.wave.size(),f)); for (DivWavetable* i: song.wave) { ERR_CHECK(fwrite(&i->len,4,1,f)); ERR_CHECK(fwrite(&i->data,4,i->len,f)); } for (int i=0; ieffectRows,f)); for (int j=0; jgetPattern(song.orders.ord[i][j],false); for (int k=0; kdata[k][0],2,1,f)); // note ERR_CHECK(fwrite(&pat->data[k][1],2,1,f)); // octave ERR_CHECK(fwrite(&pat->data[k][3],2,1,f)); // volume ERR_CHECK(fwrite(&pat->data[k][4],2,song.pat[i]->effectRows*2,f)); // volume ERR_CHECK(fwrite(&pat->data[k][2],2,1,f)); // instrument } } } ERR_CHECK(fputc(song.sample.size(),f)); for (DivSample* i: song.sample) { ERR_CHECK(fwrite(&i->length,4,1,f)); ERR_CHECK(fputc(i->name.size(),f)); ERR_CHECK(fwrite(i->name.c_str(),1,i->name.size(),f)); ERR_CHECK(fputc(i->rate,f)); ERR_CHECK(fputc(i->pitch,f)); ERR_CHECK(fputc(i->vol,f)); ERR_CHECK(fputc(i->depth,f)); ERR_CHECK(fwrite(i->data,2,i->length,f)); } return true; } // ADPCM code attribution: https://wiki.neogeodev.org/index.php?title=ADPCM_codecs static short adSteps[49]={ 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552 }; static int adStepSeek[16]={ -1, -1, -1, -1, 2, 5, 7, 9, -1, -1, -1, -1, 2, 5, 7, 9 }; static double samplePitches[11]={ 0.1666666666, 0.2, 0.25, 0.333333333, 0.5, 1, 2, 3, 4, 5, 6 }; void DivEngine::renderSamples() { if (jediTable==NULL) { jediTable=new int[16*49]; for (int step=0; step<49; step++) { for (int nib=0; nib<16; nib++) { int value=(2*(nib&0x07)+1)*adSteps[step]/8; jediTable[step*16+nib]=((nib&0x08)!=0)?-value:value; } } } for (int i=0; irendLength!=0) { delete[] s->rendData; delete[] s->adpcmRendData; } s->rendLength=(double)s->length/samplePitches[s->pitch]; s->rendData=new short[s->rendLength]; size_t adpcmLen=((s->rendLength>>1)+255)&0xffffff00; s->adpcmRendLength=adpcmLen; s->adpcmRendData=new unsigned char[adpcmLen]; memset(s->adpcmRendData,0,adpcmLen); // step 1: render to PCM int k=0; float mult=(float)(s->vol+100)/150.0f; for (double j=0; jlength; j+=samplePitches[s->pitch]) { if (k>=s->rendLength) { break; } if (s->depth==8) { float next=(float)(s->data[(unsigned int)j]-0x80)*mult; s->rendData[k++]=fmin(fmax(next,-128),127); } else { float next=(float)s->data[(unsigned int)j]*mult; s->rendData[k++]=fmin(fmax(next,-32768),32767); } } // step 2: render to ADPCM int acc=0; int decstep=0; int diff=0; int step=0; int predsample=0; int index=0; int prevsample=0; int previndex=0; for (int j=0; jrendLength; j++) { unsigned char encoded=0; int tempstep=0; predsample=prevsample; index=previndex; step=adSteps[index]; short sample=(s->depth==16)?(s->rendData[j]>>4):(s->rendData[j]<<4); diff=sample-predsample; if (diff>=0) { encoded=0; } else { encoded=8; diff=-diff; } tempstep=step; if (diff>=tempstep) { encoded|=4; diff-=tempstep; } tempstep>>=1; if (diff>=tempstep) { encoded|=2; diff-=tempstep; } tempstep>>=1; if (diff>=tempstep) encoded|=1; acc+=jediTable[decstep+encoded]; acc&=0xfff; if (acc&0x800) acc|=~0xfff; decstep+=adStepSeek[encoded&7]*16; if (decstep<0) decstep=0; if (decstep>48*16) decstep=48*16; predsample=(short)acc; index+=adStepSeek[encoded]; if (index<0) index=0; if (index>48) index=48; prevsample=predsample; previndex=index; if (j&1) { s->adpcmRendData[j>>1]|=encoded; } else { s->adpcmRendData[j>>1]=encoded<<4; } } } // step 3: allocate the samples if needed if (song.system==DIV_SYSTEM_YM2610 || song.system==DIV_SYSTEM_YM2610_EXT) { if (adpcmMem==NULL) adpcmMem=new unsigned char[16777216]; size_t memPos=0; for (int i=0; iadpcmRendLength)&0xf00000)) { memPos=(memPos+0xfffff)&0xf00000; printf("aligning to %lx.\n",memPos); } memcpy(adpcmMem+memPos,s->adpcmRendData,s->adpcmRendLength); s->rendOff=memPos; memPos+=s->adpcmRendLength; } } } DivInstrument* DivEngine::getIns(int index) { if (index<0 || index>=song.insLen) return &song.nullIns; return song.ins[index]; } DivWavetable* DivEngine::getWave(int index) { if (index<0 || index>=song.waveLen) { if (song.waveLen>0) { return song.wave[0]; } else { return &song.nullWave; } } return song.wave[index]; } void DivEngine::setLoops(int loops) { remainingLoops=loops; } void DivEngine::play() { isBusy.lock(); reset(); curRow=0; clockDrift=0; cycles=0; speedAB=false; playing=true; isBusy.unlock(); } void DivEngine::stop() { isBusy.lock(); playing=false; isBusy.unlock(); } void DivEngine::reset() { for (int i=0; i<17; i++) { chan[i]=DivChannelState(); chan[i].volMax=(dispatch->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,i))<<8)|0xff; chan[i].volume=chan[i].volMax; } dispatch->reset(); } unsigned char DivEngine::getOrder() { return curOrder; } void DivEngine::setOrder(unsigned char order) { isBusy.lock(); curOrder=order; if (order>=song.ordersLen) curOrder=0; if (playing) { reset(); curRow=0; clockDrift=0; cycles=0; speedAB=false; } isBusy.unlock(); } void DivEngine::setAudio(DivAudioEngines which) { audioEngine=which; } void DivEngine::setView(DivStatusView which) { view=which; } bool DivEngine::init(String outName) { SNDFILE* outFile; SF_INFO outInfo; if (outName!="") { // init out file got.bufsize=2048; got.rate=44100; outInfo.samplerate=got.rate; outInfo.channels=2; outInfo.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; outFile=sf_open(outName.c_str(),SFM_WRITE,&outInfo); if (outFile==NULL) { logE("could not open file for writing!\n"); return false; } } else { switch (audioEngine) { case DIV_AUDIO_JACK: #ifndef HAVE_JACK logE("Furnace was not compiled with JACK support!\n"); return false; #else output=new TAAudioJACK; #endif break; case DIV_AUDIO_SDL: output=new TAAudioSDL; break; default: logE("invalid audio engine!\n"); return false; } want.bufsize=1024; want.rate=44100; want.fragments=2; want.inChans=0; want.outChans=2; want.outFormat=TA_AUDIO_FORMAT_F32; want.name="Furnace"; output->setCallback(process,this); logI("initializing audio.\n"); if (!output->init(want,got)) { logE("error while initializing audio!\n"); return false; } } bb[0]=blip_new(32768); if (bb[0]==NULL) { logE("not enough memory!\n"); return false; } bb[1]=blip_new(32768); if (bb[1]==NULL) { logE("not enough memory!\n"); return false; } bbOut[0]=new short[got.bufsize]; bbOut[1]=new short[got.bufsize]; bbIn[0]=new short[32768]; bbIn[1]=new short[32768]; bbInLen=32768; for (int i=0; i<64; i++) { vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI)); } switch (song.system) { case DIV_SYSTEM_GENESIS: dispatch=new DivPlatformGenesis; break; case DIV_SYSTEM_GENESIS_EXT: dispatch=new DivPlatformGenesisExt; break; case DIV_SYSTEM_SMS: dispatch=new DivPlatformSMS; break; case DIV_SYSTEM_GB: dispatch=new DivPlatformGB; break; case DIV_SYSTEM_PCE: dispatch=new DivPlatformPCE; break; case DIV_SYSTEM_NES: dispatch=new DivPlatformNES; break; case DIV_SYSTEM_C64_6581: dispatch=new DivPlatformC64; ((DivPlatformC64*)dispatch)->setChipModel(true); break; case DIV_SYSTEM_C64_8580: dispatch=new DivPlatformC64; ((DivPlatformC64*)dispatch)->setChipModel(false); break; case DIV_SYSTEM_ARCADE: dispatch=new DivPlatformArcade; break; case DIV_SYSTEM_YM2610: dispatch=new DivPlatformYM2610; break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; break; } dispatch->init(this,getChannelCount(song.system),got.rate,(!song.pal) || (song.customTempo!=0 && song.hz<53)); blip_set_rates(bb[0],dispatch->rate,got.rate); blip_set_rates(bb[1],dispatch->rate,got.rate); reset(); if (outName!="") { short* ilBuffer=new short[got.bufsize*2]; // render to file remainingLoops=1; play(); while (remainingLoops) { nextBuf(NULL,NULL,0,2,got.bufsize); if (dispatch->isStereo()) { for (int i=0; isetRun(true)) { logE("error while activating!\n"); return false; } } return true; }