initial commit
took me a day to make the base... ...and ~12 hours to write a reader that reads 100% of all demo songs in 1.0
This commit is contained in:
commit
783d56c72a
26 changed files with 1660 additions and 0 deletions
31
src/engine/dispatch.h
Normal file
31
src/engine/dispatch.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
enum DivDispatchCmds {
|
||||
DIV_CMD_NOTE_ON=0,
|
||||
DIV_CMD_NOTE_OFF,
|
||||
DIV_CMD_INSTRUMENT,
|
||||
DIV_CMD_VOLUME,
|
||||
DIV_CMD_PITCH_UP,
|
||||
DIV_CMD_PITCH_DOWN,
|
||||
DIV_CMD_PITCH_TO
|
||||
};
|
||||
|
||||
struct DivCommand {
|
||||
DivDispatchCmds cmd;
|
||||
};
|
||||
|
||||
struct DivDelayedCommand {
|
||||
int ticks;
|
||||
DivCommand cmd;
|
||||
};
|
||||
|
||||
class DivDispatch {
|
||||
public:
|
||||
virtual void acquire(float& l, float& r);
|
||||
virtual int dispatch(DivCommand c);
|
||||
|
||||
/**
|
||||
* initialize this DivDispatch.
|
||||
* @param channels the number of channels to acquire.
|
||||
* @return the number of channels allocated.
|
||||
*/
|
||||
virtual int init(int channels);
|
||||
};
|
||||
630
src/engine/engine.cpp
Normal file
630
src/engine/engine.cpp
Normal file
|
|
@ -0,0 +1,630 @@
|
|||
#include "engine.h"
|
||||
#include "safeReader.h"
|
||||
#include "../ta-log.h"
|
||||
#include "../audio/sdl.h"
|
||||
#include <zlib.h>
|
||||
|
||||
void process(float** in, float** out, int inChans, int outChans, unsigned int size) {
|
||||
for (int i=0; i<outChans; i++) {
|
||||
for (unsigned int j=0; j<size; j++) {
|
||||
out[i][j]=(float)(j%128)/128.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
int 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::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;
|
||||
|
||||
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);
|
||||
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.orders.ord[i][j]>ds.ordersLen) {
|
||||
logW("pattern %d exceeds order count %d!\n",ds.orders.ord[i][j],ds.ordersLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 (ds.system!=DIV_SYSTEM_GENESIS &&
|
||||
ds.system!=DIV_SYSTEM_GENESIS_EXT &&
|
||||
ds.system!=DIV_SYSTEM_ARCADE &&
|
||||
ds.system!=DIV_SYSTEM_YM2610 &&
|
||||
ds.system!=DIV_SYSTEM_YM2610_EXT &&
|
||||
ds.system!=DIV_SYSTEM_YMU759) {
|
||||
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.lp=reader.readC();
|
||||
ins->c64.bp=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();
|
||||
}
|
||||
}
|
||||
|
||||
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++) {
|
||||
DivPattern* pat=new DivPattern;
|
||||
for (int k=0; k<ds.patLen; k++) {
|
||||
// note
|
||||
pat->data[k][0]=reader.readS();
|
||||
// octave
|
||||
pat->data[k][1]=reader.readS();
|
||||
// volume
|
||||
pat->data[k][3]=reader.readS();
|
||||
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();
|
||||
}
|
||||
chan->data.push_back(pat);
|
||||
}
|
||||
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) {
|
||||
sample->rate=0;
|
||||
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()) {
|
||||
logW("premature end of song (we are at %x, but size is %x)\n",reader.tell(),reader.size());
|
||||
}
|
||||
|
||||
song=ds;
|
||||
} catch (EndOfFileException e) {
|
||||
logE("premature end of file!\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivEngine::play() {
|
||||
|
||||
}
|
||||
|
||||
bool DivEngine::init() {
|
||||
output=new TAAudioSDL;
|
||||
want.bufsize=1024;
|
||||
want.fragments=2;
|
||||
want.inChans=0;
|
||||
want.outChans=2;
|
||||
want.outFormat=TA_AUDIO_FORMAT_F32;
|
||||
want.name="DivAudio";
|
||||
|
||||
output->setCallback(process);
|
||||
|
||||
logI("initializing audio.\n");
|
||||
if (!output->init(want,got)) {
|
||||
logE("error while initializing audio!\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!output->setRun(true)) {
|
||||
printf("error while activating!\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
35
src/engine/engine.h
Normal file
35
src/engine/engine.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#include "song.h"
|
||||
#include "dispatch.h"
|
||||
#include "../audio/taAudio.h"
|
||||
|
||||
struct DivChannelState {
|
||||
std::vector<DivDelayedCommand> delayed;
|
||||
int rampSpeed, portaSpeed, portaNote;
|
||||
int volSpeed;
|
||||
int vibratoDepth, vibratoRate;
|
||||
int tremoloDepth, tremoloRate;
|
||||
};
|
||||
|
||||
class DivEngine {
|
||||
DivSong song;
|
||||
DivDispatch* dispatch;
|
||||
TAAudio* output;
|
||||
TAAudioDesc want, got;
|
||||
int chans;
|
||||
bool playing;
|
||||
bool speedAB;
|
||||
int ticks, curRow, curOrder;
|
||||
std::vector<DivChannelState> chan;
|
||||
|
||||
public:
|
||||
// load a .dmf.
|
||||
bool load(void* f, size_t length);
|
||||
// save as .dmf.
|
||||
bool save(FILE* f);
|
||||
|
||||
// play
|
||||
void play();
|
||||
|
||||
// initialize the engine.
|
||||
bool init();
|
||||
};
|
||||
48
src/engine/instrument.h
Normal file
48
src/engine/instrument.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
enum DivInstrumentType {
|
||||
DIV_INS_FM,
|
||||
DIV_INS_STD,
|
||||
DIV_INS_GB,
|
||||
DIV_INS_C64
|
||||
};
|
||||
|
||||
struct DivInstrumentFM {
|
||||
char alg, fb, fms, ams, ops;
|
||||
struct {
|
||||
char am, ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv;
|
||||
char dam, dvb, egt, ksl, sus, vib, ws, ksr; // YMU759
|
||||
} op[4];
|
||||
};
|
||||
|
||||
struct DivInstrumentSTD {
|
||||
int volMacro[256];
|
||||
int arpMacro[256];
|
||||
int dutyMacro[256];
|
||||
int waveMacro[256];
|
||||
bool arpMacroMode;
|
||||
unsigned char volMacroLen, arpMacroLen, dutyMacroLen, waveMacroLen;
|
||||
unsigned char volMacroLoop, arpMacroLoop, dutyMacroLoop, waveMacroLoop;
|
||||
};
|
||||
|
||||
struct DivInstrumentGB {
|
||||
unsigned char envVol, envDir, envLen, soundLen;
|
||||
};
|
||||
|
||||
struct DivInstrumentC64 {
|
||||
bool triOn, sawOn, pulseOn, noiseOn;
|
||||
unsigned char a, d, s, r;
|
||||
unsigned char duty;
|
||||
unsigned char ringMod, oscSync;
|
||||
bool toFilter, volIsCutoff, initFilter;
|
||||
unsigned char res, cut;
|
||||
bool hp, lp, bp, ch3off;
|
||||
};
|
||||
|
||||
struct DivInstrument {
|
||||
String name;
|
||||
bool mode;
|
||||
DivInstrumentType type;
|
||||
DivInstrumentFM fm;
|
||||
DivInstrumentSTD std;
|
||||
DivInstrumentGB gb;
|
||||
DivInstrumentC64 c64;
|
||||
};
|
||||
3
src/engine/orders.h
Normal file
3
src/engine/orders.h
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
struct DivOrders {
|
||||
char ord[32][128];
|
||||
};
|
||||
15
src/engine/pattern.h
Normal file
15
src/engine/pattern.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
struct DivPattern {
|
||||
char data[256][16];
|
||||
};
|
||||
|
||||
struct DivChannelData {
|
||||
char effectRows;
|
||||
// data goes as follows: data[ROW][TYPE]
|
||||
// TYPE is:
|
||||
// 0: note
|
||||
// 1: octave
|
||||
// 2: instrument
|
||||
// 3: volume
|
||||
// 4-5+: effect/effect value
|
||||
std::vector<DivPattern*> data;
|
||||
};
|
||||
0
src/engine/platform/dummy.cpp
Normal file
0
src/engine/platform/dummy.cpp
Normal file
10
src/engine/platform/dummy.h
Normal file
10
src/engine/platform/dummy.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#include "../dispatch.h"
|
||||
|
||||
// the dummy platform outputs square waves, interprets STD instruments and plays samples.
|
||||
// used when a DivDispatch for a system is not found.
|
||||
class DivPlatformDummy: public DivDispatch {
|
||||
public:
|
||||
void acquire(float& l, float& r);
|
||||
int dispatch(DivCommand c);
|
||||
int init(int channels);
|
||||
};
|
||||
143
src/engine/safeReader.cpp
Normal file
143
src/engine/safeReader.cpp
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#include "safeReader.h"
|
||||
#include "../ta-log.h"
|
||||
|
||||
//#define READ_DEBUG
|
||||
|
||||
bool SafeReader::seek(ssize_t where, int whence) {
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
if (where<0) return false;
|
||||
if (where>(ssize_t)len) return false;
|
||||
curSeek=where;
|
||||
break;
|
||||
case SEEK_CUR: {
|
||||
ssize_t finalSeek=len+where;
|
||||
if (finalSeek<0) return false;
|
||||
if (finalSeek>(ssize_t)len) return false;
|
||||
curSeek=finalSeek;
|
||||
break;
|
||||
}
|
||||
case SEEK_END: {
|
||||
ssize_t finalSeek=len-where;
|
||||
if (finalSeek<0) return false;
|
||||
if (finalSeek>(ssize_t)len) return false;
|
||||
curSeek=finalSeek;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t SafeReader::tell() {
|
||||
return curSeek;
|
||||
}
|
||||
|
||||
size_t SafeReader::size() {
|
||||
return len;
|
||||
}
|
||||
|
||||
int SafeReader::read(void* where, size_t count) {
|
||||
#ifdef READ_DEBUG
|
||||
logD("SR: reading %d bytes at %x\n",count,curSeek);
|
||||
#endif
|
||||
if (count==0) return 0;
|
||||
if (curSeek+count>len) throw EndOfFileException(this,len);
|
||||
memcpy(where,&buf[curSeek],count);
|
||||
curSeek+=count;
|
||||
return count;
|
||||
}
|
||||
|
||||
char SafeReader::readC() {
|
||||
#ifdef READ_DEBUG
|
||||
logD("SR: reading char %x:\n",curSeek);
|
||||
#endif
|
||||
if (curSeek+1>len) throw EndOfFileException(this,len);
|
||||
#ifdef READ_DEBUG
|
||||
logD("SR: %.2x\n",buf[curSeek]);
|
||||
#endif
|
||||
return (signed char)buf[curSeek++];
|
||||
}
|
||||
|
||||
short SafeReader::readS() {
|
||||
#ifdef READ_DEBUG
|
||||
logD("SR: reading short %x:\n",curSeek);
|
||||
#endif
|
||||
if (curSeek+2>len) throw EndOfFileException(this,len);
|
||||
short ret=*(short*)(&buf[curSeek]);
|
||||
#ifdef READ_DEBUG
|
||||
logD("SR: %.4x\n",ret);
|
||||
#endif
|
||||
curSeek+=2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
short SafeReader::readS_BE() {
|
||||
if (curSeek+1>len) throw EndOfFileException(this,len);
|
||||
short ret=*(short*)(&buf[curSeek]);
|
||||
curSeek+=2;
|
||||
return (ret>>8)|((ret&0xff)<<8);
|
||||
}
|
||||
|
||||
int SafeReader::readI() {
|
||||
#ifdef READ_DEBUG
|
||||
logD("SR: reading int %x:\n",curSeek);
|
||||
#endif
|
||||
if (curSeek+4>len) throw EndOfFileException(this,len);
|
||||
int ret=*(int*)(&buf[curSeek]);
|
||||
curSeek+=4;
|
||||
#ifdef READ_DEBUG
|
||||
logD("SR: %.8x\n",ret);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
int SafeReader::readI_BE() {
|
||||
if (curSeek+4>len) throw EndOfFileException(this,len);
|
||||
int ret=*(int*)(&buf[curSeek]);
|
||||
curSeek+=4;
|
||||
return (ret>>24)|((ret&0xff0000)>>8)|((ret&0xff00)<<8)|((ret&0xff)<<24);
|
||||
}
|
||||
|
||||
int64_t SafeReader::readL() {
|
||||
if (curSeek+8>len) throw EndOfFileException(this,len);
|
||||
int64_t ret=*(int64_t*)(&buf[curSeek]);
|
||||
curSeek+=8;
|
||||
return ret;
|
||||
}
|
||||
|
||||
float SafeReader::readF() {
|
||||
if (curSeek+4>len) throw EndOfFileException(this,len);
|
||||
float ret=*(float*)(&buf[curSeek]);
|
||||
curSeek+=4;
|
||||
return ret;
|
||||
}
|
||||
|
||||
double SafeReader::readD() {
|
||||
if (curSeek+8>len) throw EndOfFileException(this,len);
|
||||
double ret=*(double*)(&buf[curSeek]);
|
||||
curSeek+=8;
|
||||
return ret;
|
||||
}
|
||||
|
||||
String SafeReader::readString(size_t stlen) {
|
||||
String ret;
|
||||
#ifdef READ_DEBUG
|
||||
logD("SR: reading string len %d at %x\n",stlen,curSeek);
|
||||
#endif
|
||||
size_t curPos=0;
|
||||
while (curPos<stlen) {
|
||||
char c=readC();
|
||||
if (c!=0) ret.push_back(c);
|
||||
curPos++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
String SafeReader::readString() {
|
||||
String ret;
|
||||
char c;
|
||||
while ((c=readC())!=0) {
|
||||
ret.push_back(c);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
51
src/engine/safeReader.h
Normal file
51
src/engine/safeReader.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#ifndef _SAFEREADER_H
|
||||
#define _SAFEREADER_H
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "../ta-utils.h"
|
||||
|
||||
class SafeReader;
|
||||
|
||||
struct EndOfFileException {
|
||||
SafeReader* reader;
|
||||
size_t finalSize;
|
||||
EndOfFileException(SafeReader* r, size_t fs):
|
||||
reader(r),
|
||||
finalSize(fs) {}
|
||||
};
|
||||
|
||||
class SafeReader {
|
||||
unsigned char* buf;
|
||||
size_t len;
|
||||
|
||||
size_t curSeek;
|
||||
|
||||
public:
|
||||
bool seek(ssize_t where, int whence);
|
||||
size_t tell();
|
||||
size_t size();
|
||||
|
||||
int read(void* where, size_t count);
|
||||
|
||||
// these functions may throw EndOfFileException.
|
||||
char readC();
|
||||
short readS();
|
||||
short readS_BE();
|
||||
int readI();
|
||||
int readI_BE();
|
||||
int64_t readL();
|
||||
int64_t readL_BE();
|
||||
float readF();
|
||||
float readF_BE();
|
||||
double readD();
|
||||
double readD_BE();
|
||||
String readString();
|
||||
String readString(size_t len);
|
||||
|
||||
SafeReader(void* b, size_t l):
|
||||
buf((unsigned char*)b),
|
||||
len(l),
|
||||
curSeek(0) {}
|
||||
};
|
||||
|
||||
#endif
|
||||
7
src/engine/sample.h
Normal file
7
src/engine/sample.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
struct DivSample {
|
||||
String name;
|
||||
int length, rate;
|
||||
char vol, pitch, depth;
|
||||
short* data;
|
||||
char* data8;
|
||||
};
|
||||
96
src/engine/song.h
Normal file
96
src/engine/song.h
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include "../ta-utils.h"
|
||||
#include "orders.h"
|
||||
#include "instrument.h"
|
||||
#include "pattern.h"
|
||||
#include "wavetable.h"
|
||||
#include "sample.h"
|
||||
|
||||
enum DivSystem {
|
||||
DIV_SYSTEM_NULL=0,
|
||||
DIV_SYSTEM_YMU759,
|
||||
DIV_SYSTEM_GENESIS,
|
||||
DIV_SYSTEM_GENESIS_EXT,
|
||||
DIV_SYSTEM_SMS,
|
||||
DIV_SYSTEM_GB,
|
||||
DIV_SYSTEM_PCE,
|
||||
DIV_SYSTEM_NES,
|
||||
DIV_SYSTEM_C64_6581,
|
||||
DIV_SYSTEM_C64_8580,
|
||||
DIV_SYSTEM_ARCADE,
|
||||
DIV_SYSTEM_YM2610,
|
||||
DIV_SYSTEM_YM2610_EXT
|
||||
};
|
||||
|
||||
struct DivSong {
|
||||
// version number used for saving the song.
|
||||
// divorce will save using the latest possible version,
|
||||
// but eventually I will and 0x80 to this value to indicate a divorce module
|
||||
// known version numbers:
|
||||
// - 24: v0.12/0.13/1.0
|
||||
// - current format version
|
||||
// - changes pattern length from char to int, probably to allow for size 256
|
||||
// - 23: ???
|
||||
// -
|
||||
// - 19: v11
|
||||
// - introduces Arcade system
|
||||
// - changes to the FM instrument format due to YMU759 being dropped
|
||||
// - 18: v10
|
||||
// - radically changes STD instrument for Game Boy
|
||||
// - 17: v9
|
||||
// - changes C64 volIsCutoff flag from int to char for unknown reasons
|
||||
// - 16: v8 (?)
|
||||
// - introduces C64 system
|
||||
// - 15: v7 (?)
|
||||
// - 14: v6 (?)
|
||||
// - introduces NES system
|
||||
// - changes macro and wave values from char to int
|
||||
// - 13: v5.1
|
||||
// - introduces PC Engine system in later version (how?)
|
||||
// - stores highlight in file
|
||||
// - 12: v5 (?)
|
||||
// - introduces Game Boy system
|
||||
// - introduces wavetables
|
||||
// - 11: ???
|
||||
// - introduces Sega Master System
|
||||
// - custom Hz support
|
||||
// - instrument type (FM/STD) present
|
||||
// - prior to this version the instrument type depended on the system
|
||||
// - 10: ???
|
||||
// - introduces multiple effect columns
|
||||
// - 9: v3.9
|
||||
// - introduces Genesis system
|
||||
// - introduces system number
|
||||
// - 7: ???
|
||||
// - 5: ???
|
||||
// - 3: BETA 3 (?)
|
||||
// - possibly the first version that could save
|
||||
// - basic format, no system number, 16 instruments, one speed, YMU759-only
|
||||
// - if somebody manages to find a version 2 or even 1 module, please tell me as it will be worth more than a luxury vehicle
|
||||
unsigned char version;
|
||||
|
||||
// system
|
||||
DivSystem system;
|
||||
|
||||
// song information
|
||||
String name, author;
|
||||
|
||||
// legacy song information
|
||||
String carrier, composer, vendor, category, writer, arranger, copyright, manGroup, manInfo, createdDate, revisionDate;
|
||||
|
||||
// highlight
|
||||
unsigned char hilightA, hilightB;
|
||||
|
||||
// module details
|
||||
unsigned char timeBase, speed1, speed2, arpLen;
|
||||
bool pal;
|
||||
bool customTempo;
|
||||
int hz, patLen, ordersLen, insLen, waveLen, sampleLen;
|
||||
|
||||
DivOrders orders;
|
||||
std::vector<DivInstrument*> ins;
|
||||
std::vector<DivChannelData*> pat;
|
||||
std::vector<DivWavetable*> wave;
|
||||
std::vector<DivSample*> sample;
|
||||
};
|
||||
4
src/engine/wavetable.h
Normal file
4
src/engine/wavetable.h
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
struct DivWavetable {
|
||||
int len;
|
||||
int data[32];
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue