mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-13 00:05:06 +00:00
2879b5e4d0
less CPU usage at the cost of some quality
1403 lines
39 KiB
C++
1403 lines
39 KiB
C++
#include "engine.h"
|
|
#include "instrument.h"
|
|
#include "safeReader.h"
|
|
#include "../ta-log.h"
|
|
#include "../audio/sdl.h"
|
|
#ifdef HAVE_JACK
|
|
#include "../audio/jack.h"
|
|
#endif
|
|
#include "platform/genesis.h"
|
|
#include "platform/genesisext.h"
|
|
#include "platform/sms.h"
|
|
#include "platform/gb.h"
|
|
#include "platform/pce.h"
|
|
#include "platform/nes.h"
|
|
#include "platform/c64.h"
|
|
#include "platform/arcade.h"
|
|
#include "platform/ym2610.h"
|
|
#include "platform/ym2610ext.h"
|
|
#include "platform/dummy.h"
|
|
#include <math.h>
|
|
#include <zlib.h>
|
|
#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* 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
|
|
{"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)
|
|
{"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
|
|
{"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)
|
|
};
|
|
|
|
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::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;
|
|
}
|
|
|
|
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;
|
|
case DIV_SYSTEM_PCE:
|
|
return 0;
|
|
default:
|
|
return 3;
|
|
}
|
|
return 3;
|
|
}
|
|
|
|
int DivEngine::getMaxWave() {
|
|
switch (song.system) {
|
|
case DIV_SYSTEM_PCE: case DIV_SYSTEM_GB:
|
|
return 63;
|
|
case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT:
|
|
return 7;
|
|
case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580:
|
|
return 8;
|
|
default:
|
|
return 0;
|
|
}
|
|
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;
|
|
|
|
ds.nullWave.len=32;
|
|
for (int i=0; i<32; i++) {
|
|
ds.nullWave.data[i]=15;
|
|
}
|
|
|
|
if (!reader.seek(16,SEEK_SET)) {
|
|
logE("premature end of file!");
|
|
return false;
|
|
}
|
|
ds.version=reader.readC();
|
|
logI("module version %d (0x%.2x)\n",ds.version,ds.version);
|
|
unsigned char sys=0;
|
|
if (ds.version<0x09) {
|
|
// V E R S I O N -> 3 <-
|
|
// AWESOME
|
|
ds.system=DIV_SYSTEM_YMU759;
|
|
} else {
|
|
sys=reader.readC();
|
|
ds.system=systemFromFile(sys);
|
|
}
|
|
if (ds.system==DIV_SYSTEM_NULL) {
|
|
logE("invalid system 0x%.2x!",sys);
|
|
return false;
|
|
}
|
|
|
|
if (ds.system==DIV_SYSTEM_YMU759 && ds.version<0x10) { // TODO
|
|
ds.vendor=reader.readString((unsigned char)reader.readC());
|
|
ds.carrier=reader.readString((unsigned char)reader.readC());
|
|
ds.category=reader.readString((unsigned char)reader.readC());
|
|
ds.name=reader.readString((unsigned char)reader.readC());
|
|
ds.author=reader.readString((unsigned char)reader.readC());
|
|
ds.writer=reader.readString((unsigned char)reader.readC());
|
|
ds.composer=reader.readString((unsigned char)reader.readC());
|
|
ds.arranger=reader.readString((unsigned char)reader.readC());
|
|
ds.copyright=reader.readString((unsigned char)reader.readC());
|
|
ds.manGroup=reader.readString((unsigned char)reader.readC());
|
|
ds.manInfo=reader.readString((unsigned char)reader.readC());
|
|
ds.createdDate=reader.readString((unsigned char)reader.readC());
|
|
ds.revisionDate=reader.readString((unsigned char)reader.readC());
|
|
logI("%s by %s\n",ds.name.c_str(),ds.author.c_str());
|
|
logI("has YMU-specific data:\n");
|
|
logI("- carrier: %s\n",ds.carrier.c_str());
|
|
logI("- category: %s\n",ds.category.c_str());
|
|
logI("- vendor: %s\n",ds.vendor.c_str());
|
|
logI("- writer: %s\n",ds.writer.c_str());
|
|
logI("- composer: %s\n",ds.composer.c_str());
|
|
logI("- arranger: %s\n",ds.arranger.c_str());
|
|
logI("- copyright: %s\n",ds.copyright.c_str());
|
|
logI("- management group: %s\n",ds.manGroup.c_str());
|
|
logI("- management info: %s\n",ds.manInfo.c_str());
|
|
logI("- created on: %s\n",ds.createdDate.c_str());
|
|
logI("- revision date: %s\n",ds.revisionDate.c_str());
|
|
} else {
|
|
ds.name=reader.readString((unsigned char)reader.readC());
|
|
ds.author=reader.readString((unsigned char)reader.readC());
|
|
logI("%s by %s\n",ds.name.c_str(),ds.author.c_str());
|
|
}
|
|
|
|
logI("reading module data...\n");
|
|
if (ds.version>0x0c) {
|
|
ds.hilightA=reader.readC();
|
|
ds.hilightB=reader.readC();
|
|
}
|
|
|
|
ds.timeBase=reader.readC();
|
|
ds.speed1=reader.readC();
|
|
if (ds.version>0x03) {
|
|
ds.speed2=reader.readC();
|
|
ds.pal=reader.readC();
|
|
ds.customTempo=reader.readC();
|
|
} else {
|
|
ds.speed2=ds.speed1;
|
|
}
|
|
if (ds.version>0x0a) {
|
|
String hz=reader.readString(3);
|
|
if (ds.customTempo) {
|
|
ds.hz=std::stoi(hz);
|
|
}
|
|
}
|
|
// TODO
|
|
if (ds.version>0x17) {
|
|
ds.patLen=reader.readI();
|
|
} else {
|
|
ds.patLen=(unsigned char)reader.readC();
|
|
}
|
|
ds.ordersLen=(unsigned char)reader.readC();
|
|
|
|
if (ds.version<20 && ds.version>3) {
|
|
ds.arpLen=reader.readC();
|
|
} else {
|
|
ds.arpLen=1;
|
|
}
|
|
|
|
logI("reading pattern matrix (%d)...\n",ds.ordersLen);
|
|
for (int i=0; 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) {
|
|
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()) {
|
|
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;
|
|
}
|
|
|
|
// ADPCM code attribution: https://wiki.neogeodev.org/index.php?title=ADPCM_codecs
|
|
|
|
static short adSteps[49]={
|
|
16, 17, 19, 21, 23, 25, 28, 31, 34, 37,
|
|
41, 45, 50, 55, 60, 66, 73, 80, 88, 97,
|
|
107, 118, 130, 143, 157, 173, 190, 209, 230, 253,
|
|
279, 307, 337, 371, 408, 449, 494, 544, 598, 658,
|
|
724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552
|
|
};
|
|
|
|
static int adStepSeek[16]={
|
|
-1, -1, -1, -1, 2, 5, 7, 9, -1, -1, -1, -1, 2, 5, 7, 9
|
|
};
|
|
|
|
static double samplePitches[11]={
|
|
0.1666666666, 0.2, 0.25, 0.333333333, 0.5,
|
|
1,
|
|
2, 3, 4, 5, 6
|
|
};
|
|
|
|
void DivEngine::renderSamples() {
|
|
if (jediTable==NULL) {
|
|
jediTable=new int[16*49];
|
|
for (int step=0; step<49; step++) {
|
|
for (int nib=0; nib<16; nib++) {
|
|
int value=(2*(nib&0x07)+1)*adSteps[step]/8;
|
|
jediTable[step*16+nib]=((nib&0x08)!=0)?-value:value;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i=0; i<song.sampleLen; i++) {
|
|
DivSample* s=song.sample[i];
|
|
if (s->rendLength!=0) {
|
|
delete[] s->rendData;
|
|
delete[] s->adpcmRendData;
|
|
}
|
|
s->rendLength=(double)s->length/samplePitches[s->pitch];
|
|
s->rendData=new short[s->rendLength];
|
|
size_t adpcmLen=((s->rendLength>>1)+255)&0xffffff00;
|
|
s->adpcmRendLength=adpcmLen;
|
|
s->adpcmRendData=new unsigned char[adpcmLen];
|
|
memset(s->adpcmRendData,0,adpcmLen);
|
|
|
|
// step 1: render to PCM
|
|
unsigned int k=0;
|
|
float mult=(float)(s->vol)/50.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);
|
|
}
|
|
}
|
|
|
|
// 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 (unsigned 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;
|
|
decstep+=adStepSeek[encoded&7]*16;
|
|
if (decstep<0) decstep=0;
|
|
if (decstep>48*16) decstep=48*16;
|
|
predsample=(short)acc;
|
|
|
|
index+=adStepSeek[encoded];
|
|
if (index<0) index=0;
|
|
if (index>48) index=48;
|
|
|
|
prevsample=predsample;
|
|
previndex=index;
|
|
|
|
if (j&1) {
|
|
s->adpcmRendData[j>>1]|=encoded;
|
|
} else {
|
|
s->adpcmRendData[j>>1]=encoded<<4;
|
|
}
|
|
}
|
|
}
|
|
|
|
// step 3: allocate the samples if needed
|
|
if (song.system==DIV_SYSTEM_YM2610 || song.system==DIV_SYSTEM_YM2610_EXT) {
|
|
if (adpcmMem==NULL) adpcmMem=new unsigned char[16777216];
|
|
|
|
size_t memPos=0;
|
|
for (int i=0; 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() {
|
|
isBusy.lock();
|
|
reset();
|
|
curRow=0;
|
|
clockDrift=0;
|
|
cycles=0;
|
|
ticks=1;
|
|
speedAB=false;
|
|
playing=true;
|
|
isBusy.unlock();
|
|
}
|
|
|
|
void DivEngine::stop() {
|
|
isBusy.lock();
|
|
playing=false;
|
|
isBusy.unlock();
|
|
}
|
|
|
|
void DivEngine::reset() {
|
|
for (int i=0; i<17; i++) {
|
|
chan[i]=DivChannelState();
|
|
chan[i].volMax=(dispatch->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,i))<<8)|0xff;
|
|
chan[i].volume=chan[i].volMax;
|
|
}
|
|
dispatch->reset();
|
|
}
|
|
|
|
int DivEngine::getMaxVolumeChan(int ch) {
|
|
return chan[ch].volMax>>8;
|
|
}
|
|
|
|
unsigned char DivEngine::getOrder() {
|
|
return curOrder;
|
|
}
|
|
|
|
int DivEngine::getRow() {
|
|
return curRow;
|
|
}
|
|
|
|
bool DivEngine::isPlaying() {
|
|
return playing;
|
|
}
|
|
|
|
void DivEngine::setOrder(unsigned char order) {
|
|
isBusy.lock();
|
|
curOrder=order;
|
|
if (order>=song.ordersLen) curOrder=0;
|
|
if (playing) {
|
|
reset();
|
|
curRow=0;
|
|
clockDrift=0;
|
|
cycles=0;
|
|
ticks=1;
|
|
speedAB=false;
|
|
}
|
|
isBusy.unlock();
|
|
}
|
|
|
|
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;
|
|
((DivPlatformArcade*)dispatch)->setYMFM(true);
|
|
break;
|
|
case DIV_SYSTEM_YM2610:
|
|
dispatch=new DivPlatformYM2610;
|
|
break;
|
|
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;
|
|
isBusy.lock();
|
|
dispatch->quit();
|
|
delete dispatch;
|
|
dispatch=NULL;
|
|
chans=0;
|
|
playing=false;
|
|
speedAB=false;
|
|
endOfSong=false;
|
|
ticks=0;
|
|
cycles=0;
|
|
curRow=0;
|
|
curOrder=0;
|
|
nextSpeed=3;
|
|
clockDrift=0;
|
|
changeOrd=-1;
|
|
changePos=0;
|
|
totalTicks=0;
|
|
totalCmds=0;
|
|
lastCmds=0;
|
|
cmdsPerSecond=0;
|
|
isBusy.unlock();
|
|
}
|
|
|
|
bool DivEngine::init(String outName) {
|
|
SNDFILE* outFile;
|
|
SF_INFO outInfo;
|
|
if (outName!="") {
|
|
// init out file
|
|
got.bufsize=2048;
|
|
got.rate=44100;
|
|
|
|
outInfo.samplerate=got.rate;
|
|
outInfo.channels=2;
|
|
outInfo.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
|
|
|
|
outFile=sf_open(outName.c_str(),SFM_WRITE,&outInfo);
|
|
if (outFile==NULL) {
|
|
logE("could not open file for writing!\n");
|
|
return false;
|
|
}
|
|
} else {
|
|
switch (audioEngine) {
|
|
case DIV_AUDIO_JACK:
|
|
#ifndef HAVE_JACK
|
|
logE("Furnace was not compiled with JACK support!\n");
|
|
return false;
|
|
#else
|
|
output=new TAAudioJACK;
|
|
#endif
|
|
break;
|
|
case DIV_AUDIO_SDL:
|
|
output=new TAAudioSDL;
|
|
break;
|
|
default:
|
|
logE("invalid audio engine!\n");
|
|
return false;
|
|
}
|
|
want.bufsize=1024;
|
|
want.rate=44100;
|
|
want.fragments=2;
|
|
want.inChans=0;
|
|
want.outChans=2;
|
|
want.outFormat=TA_AUDIO_FORMAT_F32;
|
|
want.name="Furnace";
|
|
|
|
output->setCallback(process,this);
|
|
|
|
logI("initializing audio.\n");
|
|
if (!output->init(want,got)) {
|
|
logE("error while initializing audio!\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bb[0]=blip_new(32768);
|
|
if (bb[0]==NULL) {
|
|
logE("not enough memory!\n");
|
|
return false;
|
|
}
|
|
|
|
bb[1]=blip_new(32768);
|
|
if (bb[1]==NULL) {
|
|
logE("not enough memory!\n");
|
|
return false;
|
|
}
|
|
|
|
bbOut[0]=new short[got.bufsize];
|
|
bbOut[1]=new short[got.bufsize];
|
|
|
|
bbIn[0]=new short[32768];
|
|
bbIn[1]=new short[32768];
|
|
bbInLen=32768;
|
|
|
|
for (int i=0; i<64; i++) {
|
|
vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI));
|
|
}
|
|
|
|
initDispatch();
|
|
reset();
|
|
|
|
if (outName!="") {
|
|
short* ilBuffer=new short[got.bufsize*2];
|
|
// render to file
|
|
remainingLoops=1;
|
|
play();
|
|
while (remainingLoops) {
|
|
nextBuf(NULL,NULL,0,2,got.bufsize);
|
|
|
|
if (dispatch->isStereo()) {
|
|
for (size_t i=0; i<got.bufsize; i++) {
|
|
ilBuffer[i<<1]=bbOut[0][i];
|
|
ilBuffer[1+(i<<1)]=bbOut[1][i];
|
|
}
|
|
} else {
|
|
for (size_t i=0; i<got.bufsize; i++) {
|
|
ilBuffer[i<<1]=bbOut[0][i];
|
|
ilBuffer[1+(i<<1)]=bbOut[0][i];
|
|
}
|
|
}
|
|
|
|
if (!remainingLoops) {
|
|
sf_writef_short(outFile,ilBuffer,totalProcessed);
|
|
} else {
|
|
sf_writef_short(outFile,ilBuffer,got.bufsize);
|
|
}
|
|
}
|
|
delete[] ilBuffer;
|
|
sf_close(outFile);
|
|
return true;
|
|
} else {
|
|
if (!output->setRun(true)) {
|
|
logE("error while activating!\n");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DivEngine::quit() {
|
|
output->quit();
|
|
quitDispatch();
|
|
return true;
|
|
}
|