furnace/src/engine/engine.cpp

1382 lines
38 KiB
C++
Raw Normal View History

#include "engine.h"
#include "instrument.h"
#include "safeReader.h"
#include "../ta-log.h"
#include "../audio/sdl.h"
2021-06-09 08:33:03 +00:00
#ifdef HAVE_JACK
#include "../audio/jack.h"
#endif
2021-05-12 22:19:18 +00:00
#include "platform/genesis.h"
2021-05-16 22:43:10 +00:00
#include "platform/genesisext.h"
#include "platform/sms.h"
2021-05-26 08:17:12 +00:00
#include "platform/gb.h"
#include "platform/pce.h"
2021-12-04 06:19:54 +00:00
#include "platform/nes.h"
2021-12-05 04:55:28 +00:00
#include "platform/c64.h"
2021-12-08 22:40:35 +00:00
#include "platform/arcade.h"
2021-12-09 18:25:02 +00:00
#include "platform/ym2610.h"
2021-12-14 19:31:57 +00:00
#include "platform/ym2610ext.h"
#include "platform/dummy.h"
#include <math.h>
#include <zlib.h>
2021-12-07 17:21:23 +00:00
#include <sndfile.h>
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);
}
2021-12-13 18:10:56 +00:00
const char* chanNames[11][17]={
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis
2021-12-13 22:09:46 +00:00
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3)
2021-12-13 18:10:56 +00:00
{"Square 1", "Square 2", "Square 3", "Noise"}, // SMS
{"Pulse 1", "Pulse 2", "Wavetable", "Noise"}, // GB
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"}, // PCE
{"Pulse 1", "Pulse 2", "Triangle", "Noise", "PCM"}, // NES
{"Channel 1", "Channel 2", "Channel 3"}, // C64
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "Sample 1", "Sample 2", "Sample 3", "Sample 4", "Sample 5"}, // Arcade
{"FM 1", "FM 2", "FM 3", "FM 4", "Square 1", "Square 2", "Square 3", "Sample 1", "Sample 2", "Sample 3", "Sample 4", "Sample 5", "Sample 6"}, // YM2610
2021-12-13 22:09:46 +00:00
{"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "Square 1", "Square 2", "Square 3", "Sample 1", "Sample 2", "Sample 3", "Sample 4", "Sample 5", "Sample 6"}, // YM2610 (extended channel 2)
2021-12-13 18:10:56 +00:00
};
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)
};
2021-12-13 18:10:56 +00:00
const char* DivEngine::getChannelName(int chan) {
switch (song.system) {
case DIV_SYSTEM_NULL: case DIV_SYSTEM_YMU759:
return chanNames[0][chan];
break;
case DIV_SYSTEM_GENESIS:
return chanNames[1][chan];
break;
case DIV_SYSTEM_GENESIS_EXT:
return chanNames[2][chan];
break;
case DIV_SYSTEM_SMS:
return chanNames[3][chan];
break;
case DIV_SYSTEM_GB:
return chanNames[4][chan];
break;
case DIV_SYSTEM_PCE:
return chanNames[5][chan];
break;
case DIV_SYSTEM_NES:
return chanNames[6][chan];
break;
case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580:
return chanNames[7][chan];
break;
case DIV_SYSTEM_ARCADE:
return chanNames[8][chan];
break;
case DIV_SYSTEM_YM2610:
return chanNames[9][chan];
break;
case DIV_SYSTEM_YM2610_EXT:
return chanNames[10][chan];
break;
}
return "??";
}
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;
}
2021-12-12 23:19:43 +00:00
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;
2021-12-13 07:03:36 +00:00
case DIV_SYSTEM_PCE:
return 0;
2021-12-12 23:19:43 +00:00
default:
return 3;
}
return 3;
}
int DivEngine::getMaxWave() {
switch (song.system) {
case DIV_SYSTEM_PCE: case DIV_SYSTEM_GB:
2021-12-13 07:03:36 +00:00
return 63;
2021-12-12 23:19:43 +00:00
case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT:
return 7;
case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580:
return 8;
default:
2021-12-13 07:03:36 +00:00
return 0;
2021-12-12 23:19:43 +00:00
}
2021-12-13 07:03:36 +00:00
return 0;
2021-12-12 23:19:43 +00:00
}
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<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) {
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);
2021-05-11 20:26:38 +00:00
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; i<getChannelCount(ds.system); i++) {
for (int j=0; j<ds.ordersLen; j++) {
ds.orders.ord[i][j]=reader.readC();
}
}
if (ds.version>0x03) {
ds.insLen=reader.readC();
} else {
ds.insLen=16;
}
logI("reading instruments (%d)...\n",ds.insLen);
for (int i=0; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument;
if (ds.version>0x03) {
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; j<ins->fm.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; j<ins->std.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; j<ins->std.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; j<ins->std.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; j<ins->std.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; i<ds.waveLen; i++) {
DivWavetable* wave=new DivWavetable;
wave->len=(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; j<wave->len; 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; i<getChannelCount(ds.system); i++) {
DivChannelData& chan=ds.pat[i];
if (ds.version<0x0a) {
chan.effectRows=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; j<ds.ordersLen; j++) {
DivPattern* pat=chan.getPattern(ds.orders.ord[i][j],true);
for (int k=0; k<ds.patLen; k++) {
// note
pat->data[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; l<chan.effectRows; 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.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; i<ds.sampleLen; i++) {
DivSample* sample=new DivSample;
sample->length=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) {
2021-06-09 06:16:26 +00:00
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()<reader.size()) {
2021-12-09 07:58:53 +00:00
if ((reader.tell()+1)!=reader.size()) {
logW("premature end of song (we are at %x, but size is %x)\n",reader.tell(),reader.size());
}
}
song=ds;
chans=getChannelCount(song.system);
renderSamples();
} catch (EndOfFileException e) {
logE("premature end of file!\n");
return false;
}
return true;
}
#define ERR_CHECK(x) x; if (ferror(f)) return false;
bool DivEngine::save(FILE* f) {
// write magic
ERR_CHECK(fwrite(DIV_DMF_MAGIC,1,16,f));
// version
ERR_CHECK(fputc(24,f));
ERR_CHECK(fputc(systemToFile(song.system),f));
// song info
ERR_CHECK(fputc(song.name.size(),f));
ERR_CHECK(fwrite(song.name.c_str(),1,song.name.size(),f));
ERR_CHECK(fputc(song.author.size(),f));
ERR_CHECK(fwrite(song.author.c_str(),1,song.author.size(),f));
ERR_CHECK(fputc(song.hilightA,f));
ERR_CHECK(fputc(song.hilightB,f));
ERR_CHECK(fputc(song.timeBase,f));
ERR_CHECK(fputc(song.speed1,f));
ERR_CHECK(fputc(song.speed2,f));
ERR_CHECK(fputc(song.pal,f));
ERR_CHECK(fputc(song.customTempo,f));
char customHz[4];
memset(customHz,0,4);
snprintf(customHz,4,"%d",song.hz);
ERR_CHECK(fwrite(customHz,1,3,f));
ERR_CHECK(fwrite(&song.patLen,4,1,f));
ERR_CHECK(fputc(song.ordersLen,f));
for (int i=0; i<getChannelCount(song.system); i++) {
for (int j=0; j<song.ordersLen; j++) {
ERR_CHECK(fputc(song.orders.ord[i][j],f));
}
}
ERR_CHECK(fputc(song.ins.size(),f));
for (DivInstrument* i: song.ins) {
ERR_CHECK(fputc(i->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; i<getChannelCount(song.system); i++) {
ERR_CHECK(fputc(song.pat[i].effectRows,f));
for (int j=0; j<song.ordersLen; j++) {
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][j],false);
for (int k=0; k<song.patLen; k++) {
ERR_CHECK(fwrite(&pat->data[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;
}
2021-12-10 09:22:13 +00:00
// 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() {
2021-12-10 09:22:13 +00:00
if (jediTable==NULL) {
jediTable=new int[16*49];
2021-12-10 20:16:58 +00:00
for (int step=0; step<49; step++) {
for (int nib=0; nib<16; nib++) {
2021-12-10 09:22:13 +00:00
int value=(2*(nib&0x07)+1)*adSteps[step]/8;
jediTable[step*16+nib]=((nib&0x08)!=0)?-value:value;
}
}
}
for (int i=0; i<song.sampleLen; i++) {
DivSample* s=song.sample[i];
2021-12-10 09:22:13 +00:00
if (s->rendLength!=0) {
delete[] s->rendData;
delete[] s->adpcmRendData;
}
s->rendLength=(double)s->length/samplePitches[s->pitch];
s->rendData=new short[s->rendLength];
2021-12-10 09:22:13 +00:00
size_t adpcmLen=((s->rendLength>>1)+255)&0xffffff00;
2021-12-11 03:51:50 +00:00
s->adpcmRendLength=adpcmLen;
2021-12-10 09:22:13 +00:00
s->adpcmRendData=new unsigned char[adpcmLen];
memset(s->adpcmRendData,0,adpcmLen);
// step 1: render to PCM
2021-12-14 17:33:26 +00:00
unsigned int k=0;
float mult=(float)(s->vol+100)/150.0f;
for (double j=0; j<s->length; 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);
}
}
2021-12-10 09:22:13 +00:00
// 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;
2021-12-14 17:33:26 +00:00
for (unsigned int j=0; j<s->rendLength; j++) {
2021-12-10 09:22:13 +00:00
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;
2021-12-10 20:16:58 +00:00
decstep+=adStepSeek[encoded&7]*16;
2021-12-10 09:22:13 +00:00
if (decstep<0) decstep=0;
if (decstep>48*16) decstep=48*16;
predsample=(short)acc;
2021-12-10 20:16:58 +00:00
index+=adStepSeek[encoded];
2021-12-10 09:22:13 +00:00
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;
}
}
}
2021-12-11 04:41:00 +00:00
// 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; i<song.sampleLen; i++) {
DivSample* s=song.sample[i];
if ((memPos&0xf00000)!=((memPos+s->adpcmRendLength)&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() {
2021-12-11 08:34:43 +00:00
isBusy.lock();
2021-12-11 21:51:34 +00:00
reset();
2021-12-11 08:34:43 +00:00
curRow=0;
clockDrift=0;
cycles=0;
2021-12-13 07:03:36 +00:00
ticks=1;
speedAB=false;
2021-12-11 21:51:34 +00:00
playing=true;
2021-12-11 08:34:43 +00:00
isBusy.unlock();
}
void DivEngine::stop() {
isBusy.lock();
playing=false;
isBusy.unlock();
}
2021-12-11 21:51:34 +00:00
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();
}
2021-12-13 22:09:46 +00:00
int DivEngine::getMaxVolumeChan(int ch) {
return chan[ch].volMax>>8;
}
2021-12-11 08:34:43 +00:00
unsigned char DivEngine::getOrder() {
return curOrder;
}
2021-12-13 22:09:46 +00:00
int DivEngine::getRow() {
return curRow;
}
bool DivEngine::isPlaying() {
return playing;
}
2021-12-11 08:34:43 +00:00
void DivEngine::setOrder(unsigned char order) {
isBusy.lock();
curOrder=order;
if (order>=song.ordersLen) curOrder=0;
if (playing) {
2021-12-11 21:51:34 +00:00
reset();
2021-12-11 08:34:43 +00:00
curRow=0;
clockDrift=0;
cycles=0;
2021-12-13 07:03:36 +00:00
ticks=1;
speedAB=false;
2021-12-11 08:34:43 +00:00
}
isBusy.unlock();
}
2021-06-09 08:33:03 +00:00
void DivEngine::setAudio(DivAudioEngines which) {
audioEngine=which;
}
void DivEngine::setView(DivStatusView which) {
view=which;
}
void DivEngine::initDispatch() {
if (dispatch!=NULL) return;
isBusy.lock();
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;
2021-12-14 19:31:57 +00:00
case DIV_SYSTEM_YM2610_EXT:
dispatch=new DivPlatformYM2610Ext;
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);
isBusy.unlock();
}
void DivEngine::quitDispatch() {
if (dispatch==NULL) return;
}
2021-12-07 09:39:52 +00:00
bool DivEngine::init(String outName) {
2021-12-07 17:21:23 +00:00
SNDFILE* outFile;
SF_INFO outInfo;
2021-12-07 09:39:52 +00:00
if (outName!="") {
// init out file
got.bufsize=2048;
got.rate=44100;
2021-12-07 17:21:23 +00:00
outInfo.samplerate=got.rate;
2021-12-07 17:33:55 +00:00
outInfo.channels=2;
2021-12-07 17:21:23 +00:00
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;
}
2021-12-07 09:39:52 +00:00
} 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");
2021-06-09 08:33:03 +00:00
return false;
2021-12-07 09:39:52 +00:00
}
}
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));
}
initDispatch();
2021-12-11 21:51:34 +00:00
reset();
2021-12-07 09:39:52 +00:00
if (outName!="") {
2021-12-07 17:33:55 +00:00
short* ilBuffer=new short[got.bufsize*2];
2021-12-07 09:39:52 +00:00
// render to file
2021-12-07 17:21:23 +00:00
remainingLoops=1;
2021-12-12 20:22:27 +00:00
play();
2021-12-07 17:21:23 +00:00
while (remainingLoops) {
nextBuf(NULL,NULL,0,2,got.bufsize);
2021-12-07 17:33:55 +00:00
if (dispatch->isStereo()) {
2021-12-14 17:33:26 +00:00
for (size_t i=0; i<got.bufsize; i++) {
ilBuffer[i<<1]=bbOut[0][i];
ilBuffer[1+(i<<1)]=bbOut[1][i];
2021-12-07 17:33:55 +00:00
}
} else {
2021-12-14 17:33:26 +00:00
for (size_t i=0; i<got.bufsize; i++) {
ilBuffer[i<<1]=bbOut[0][i];
ilBuffer[1+(i<<1)]=bbOut[0][i];
2021-12-07 17:33:55 +00:00
}
}
2021-12-08 07:57:41 +00:00
if (!remainingLoops) {
sf_writef_short(outFile,ilBuffer,totalProcessed);
} else {
sf_writef_short(outFile,ilBuffer,got.bufsize);
}
2021-12-07 17:21:23 +00:00
}
2021-12-07 17:33:55 +00:00
delete[] ilBuffer;
2021-12-07 17:21:23 +00:00
sf_close(outFile);
return true;
2021-12-07 09:39:52 +00:00
} else {
if (!output->setRun(true)) {
logE("error while activating!\n");
return false;
}
}
return true;
}
2021-12-13 22:09:46 +00:00
bool DivEngine::quit() {
output->quit();
quitDispatch();
return true;
2021-12-14 17:33:26 +00:00
}