furnace/src/engine/engine.cpp

1267 lines
35 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"
#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);
}
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;
}
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=new DivChannelData;
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++) {
2021-12-09 06:44:40 +00:00
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.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; 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<song.pat.size(); 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
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;
for (int j=0; j<s->rendLength; 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;
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;
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-11 08:34:43 +00:00
unsigned char DivEngine::getOrder() {
return curOrder;
}
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;
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;
}
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));
}
2021-05-12 22:19:18 +00:00
switch (song.system) {
case DIV_SYSTEM_GENESIS:
dispatch=new DivPlatformGenesis;
break;
2021-05-16 22:43:10 +00:00
case DIV_SYSTEM_GENESIS_EXT:
dispatch=new DivPlatformGenesisExt;
break;
case DIV_SYSTEM_SMS:
dispatch=new DivPlatformSMS;
break;
2021-05-26 08:17:12 +00:00
case DIV_SYSTEM_GB:
dispatch=new DivPlatformGB;
break;
case DIV_SYSTEM_PCE:
dispatch=new DivPlatformPCE;
break;
2021-12-04 06:19:54 +00:00
case DIV_SYSTEM_NES:
dispatch=new DivPlatformNES;
break;
2021-12-05 04:55:28 +00:00
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;
2021-12-08 22:40:35 +00:00
case DIV_SYSTEM_ARCADE:
dispatch=new DivPlatformArcade;
break;
2021-12-09 18:25:02 +00:00
case DIV_SYSTEM_YM2610:
dispatch=new DivPlatformYM2610;
break;
2021-05-12 22:19:18 +00:00
default:
2021-06-09 17:28:46 +00:00
logW("this system is not supported yet! using dummy platform.\n");
2021-05-12 22:19:18 +00:00
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);
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;
while (remainingLoops) {
nextBuf(NULL,NULL,0,2,got.bufsize);
2021-12-07 17:33:55 +00:00
if (dispatch->isStereo()) {
for (int 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 {
for (int 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;
}