Merge branch 'tildearrow:master' into master

This commit is contained in:
BlastBrothers 2022-02-24 22:08:32 -05:00 committed by GitHub
commit 1326de3928
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 771 additions and 457 deletions

3
.gitmodules vendored
View file

@ -16,3 +16,6 @@
[submodule "extern/fmt"] [submodule "extern/fmt"]
path = extern/fmt path = extern/fmt
url = https://github.com/fmtlib/fmt.git url = https://github.com/fmtlib/fmt.git
[submodule "extern/adpcm"]
path = extern/adpcm
url = https://github.com/superctr/adpcm

View file

@ -236,6 +236,12 @@ extern/SAASound/src/SAANoise.cpp
extern/SAASound/src/SAASndC.cpp extern/SAASound/src/SAASndC.cpp
extern/SAASound/src/SAASound.cpp extern/SAASound/src/SAASound.cpp
extern/adpcm/bs_codec.c
extern/adpcm/oki_codec.c
extern/adpcm/yma_codec.c
extern/adpcm/ymb_codec.c
extern/adpcm/ymz_codec.c
extern/Nuked-OPN2/ym3438.c extern/Nuked-OPN2/ym3438.c
extern/opm/opm.c extern/opm/opm.c
src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/sn76496.cpp

BIN
demos/spamton.fur Normal file

Binary file not shown.

1
extern/adpcm vendored Submodule

@ -0,0 +1 @@
Subproject commit ef7a217154badc3b99978ac481b268c8aab67bd8

View file

@ -25,6 +25,8 @@ furthermore, an `or reserved` indicates this field is always present, but is res
the format versions are: the format versions are:
- 59: Furnace dev59
- 58: Furnace dev58
- 57: Furnace dev57 - 57: Furnace dev57
- 53: Furnace 0.5.7 - 53: Furnace 0.5.7
@ -100,17 +102,18 @@ size | description
| - possible soundchips: | - possible soundchips:
| - 0x00: end of list | - 0x00: end of list
| - 0x01: YMU759 - 17 channels | - 0x01: YMU759 - 17 channels
| - 0x02: Genesis - 10 channels | - 0x02: Genesis - 10 channels (compound!)
| - 0x03: SMS (SN76489) - 4 channels | - 0x03: SMS (SN76489) - 4 channels
| - 0x04: Game Boy - 4 channels | - 0x04: Game Boy - 4 channels
| - 0x05: PC Engine - 6 channels | - 0x05: PC Engine - 6 channels
| - 0x06: NES - 5 channels | - 0x06: NES - 5 channels
| - 0x07: C64 (8580) - 3 channels | - 0x07: C64 (8580) - 3 channels
| - 0x08: Arcade (YM2151) - 13 channels | - 0x08: Arcade (YM2151+SegaPCM) - 13 channels (compound!)
| - 0x09: Neo Geo (YM2610) - 13 channels | - 0x09: Neo Geo (YM2610) - 13 channels
| - bit 6 enables alternate mode: | - bit 6 enables alternate mode:
| - 0x42: Genesis extended - 13 channels | - 0x42: Genesis extended - 13 channels
| - 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels | - 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels (compound!)
| - 0x46: NES + VRC7 - 11 channels (compound!)
| - 0x47: C64 (6581) - 3 channels | - 0x47: C64 (6581) - 3 channels
| - 0x49: Neo Geo extended - 16 channels | - 0x49: Neo Geo extended - 16 channels
| - bit 7 for non-DefleMask chips: | - bit 7 for non-DefleMask chips:
@ -156,6 +159,8 @@ size | description
| - 0xa7: OPLL drums (YM2413) - 11 channels | - 0xa7: OPLL drums (YM2413) - 11 channels
| - 0xa8: Atari Lynx - 4 channels | - 0xa8: Atari Lynx - 4 channels
| - 0xe0: QSound - 19 channels | - 0xe0: QSound - 19 channels
| - (compound!) means that the system is composed of two or more chips,
| and has to be flattened.
32 | sound chip volumes 32 | sound chip volumes
| - signed char, 64=1.0, 127=~2.0 | - signed char, 64=1.0, 127=~2.0
32 | sound chip panning 32 | sound chip panning
@ -198,6 +203,8 @@ size | description
S?? | channel short names S?? | channel short names
| - same as above | - same as above
STR | song comment STR | song comment
4f | master volume, 1.0f=100% (>=59)
| this is 2.0f for modules before 59
# instrument # instrument
@ -426,14 +433,26 @@ size | description
STR | sample name STR | sample name
4 | length 4 | length
4 | rate 4 | rate
2 | volume 2 | volume (<58) or reserved
2 | pitch 2 | pitch (<58) or reserved
1 | depth 1 | depth
| - 0: ZX Spectrum overlay drum (1-bit)
| - 1: 1-bit NES DPCM (1-bit)
| - 4: QSound ADPCM
| - 5: ADPCM-A
| - 6: ADPCM-B
| - 7: X68000 ADPCM
| - 8: 8-bit PCM
| - 9: BRR (SNES)
| - 10: VOX
| - 16: 16-bit PCM
1 | reserved 1 | reserved
2 | C-4 rate (>=32) or reserved 2 | C-4 rate (>=32) or reserved
4 | loop point (>=19) or reserved 4 | loop point (>=19) or reserved
| - -1 means no loop | - -1 means no loop
2?? | sample data (always 16-bit) ??? | sample data
| - version<58 size is length*2
| - version>=58 size is length
# pattern # pattern

View file

@ -434,26 +434,6 @@ void DivEngine::notifyWaveChange(int wave) {
isBusy.unlock(); isBusy.unlock();
} }
// 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::renderSamplesP() { void DivEngine::renderSamplesP() {
isBusy.lock(); isBusy.lock();
renderSamples(); renderSamples();
@ -463,152 +443,46 @@ void DivEngine::renderSamplesP() {
void DivEngine::renderSamples() { void DivEngine::renderSamples() {
sPreview.sample=-1; sPreview.sample=-1;
sPreview.pos=0; sPreview.pos=0;
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;
}
}
}
// step 1: render samples
for (int i=0; i<song.sampleLen; i++) { for (int i=0; i<song.sampleLen; i++) {
DivSample* s=song.sample[i]; song.sample[i]->render();
if (s->rendLength!=0) {
delete[] s->rendData;
delete[] s->adpcmRendData;
}
s->rendLength=(double)s->length/samplePitches[s->pitch];
if (s->rendLength==0) {
s->adpcmRendLength=0;
continue;
}
s->rendData=new short[s->rendLength];
size_t adpcmLen=((s->rendLength>>1)+255)&0xffffff00;
if (adpcmLen>1048576) adpcmLen=1048576;
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->adpcmRendLength*2; j++) {
unsigned char encoded=0;
int tempstep=0;
predsample=prevsample;
index=previndex;
step=adSteps[index];
short sample=(j<s->rendLength)?((s->depth==16)?(s->rendData[j]>>4):(s->rendData[j]<<4)):0;
if (sample>0x7d0) sample=0x7d0;
if (sample<-0x7d0) sample=-0x7d0;
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];
/*if (acc>0x7ff || acc<-0x800) {
logW("clipping! %d\n",acc);
}*/
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 ADPCM samples // step 2: allocate ADPCM-A samples
if (adpcmMem==NULL) adpcmMem=new unsigned char[16777216]; if (adpcmAMem==NULL) adpcmAMem=new unsigned char[16777216];
size_t memPos=0; size_t memPos=0;
for (int i=0; i<song.sampleLen; i++) { for (int i=0; i<song.sampleLen; i++) {
DivSample* s=song.sample[i]; DivSample* s=song.sample[i];
if ((memPos&0xf00000)!=((memPos+s->adpcmRendLength)&0xf00000)) { int paddedLen=(s->lengthA+255)&(~0xff);
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
memPos=(memPos+0xfffff)&0xf00000; memPos=(memPos+0xfffff)&0xf00000;
} }
if (memPos>=16777216) { if (memPos>=16777216) {
logW("out of ADPCM memory for sample %d!\n",i); logW("out of ADPCM memory for sample %d!\n",i);
break; break;
} }
if (memPos+s->adpcmRendLength>=16777216) { if (memPos+paddedLen>=16777216) {
memcpy(adpcmMem+memPos,s->adpcmRendData,16777216-memPos); memcpy(adpcmAMem+memPos,s->dataA,16777216-memPos);
logW("out of ADPCM memory for sample %d!\n",i); logW("out of ADPCM memory for sample %d!\n",i);
} else { } else {
memcpy(adpcmMem+memPos,s->adpcmRendData,s->adpcmRendLength); memcpy(adpcmAMem+memPos,s->dataA,paddedLen);
} }
s->rendOff=memPos; s->offA=memPos;
memPos+=s->adpcmRendLength; memPos+=paddedLen;
} }
adpcmMemLen=memPos+256; adpcmAMemLen=memPos+256;
// step 4: allocate qsound pcm samples // step 4: allocate qsound pcm samples
if (qsoundMem==NULL) qsoundMem=new unsigned char[16777216]; if (qsoundMem==NULL) qsoundMem=new unsigned char[16777216];
memset(qsoundMem, 0, 16777216);
memPos=0; memPos=0;
for (int i=0; i<song.sampleLen; i++) { for (int i=0; i<song.sampleLen; i++) {
DivSample* s=song.sample[i]; DivSample* s=song.sample[i];
int length = s->rendLength; int length=s->length8;
if (length > 65536-16) { if (length>65536-16) {
length = 65536-16; length=65536-16;
} }
if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) { if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) {
memPos=(memPos+0xffff)&0xff0000; memPos=(memPos+0xffff)&0xff0000;
@ -619,15 +493,15 @@ void DivEngine::renderSamples() {
} }
if (memPos+length>=16777216) { if (memPos+length>=16777216) {
for (unsigned int i=0; i<16777216-(memPos+length); i++) { for (unsigned int i=0; i<16777216-(memPos+length); i++) {
qsoundMem[(memPos + i) ^ 0x8000] = s->rendData[i] >> ((s->depth == 16) ? 8 : 0); qsoundMem[(memPos+i)^0x8000]=s->data8[i];
} }
logW("out of QSound PCM memory for sample %d!\n",i); logW("out of QSound PCM memory for sample %d!\n",i);
} else { } else {
for (int i=0; i<length; i++) { for (int i=0; i<length; i++) {
qsoundMem[(memPos + i) ^ 0x8000] = s->rendData[i] >> ((s->depth == 16) ? 8 : 0); qsoundMem[(memPos+i)^0x8000]=s->data8[i];
} }
} }
s->rendOffQsound=memPos ^ 0x8000; s->offQSound=memPos^0x8000;
memPos+=length+16; memPos+=length+16;
} }
qsoundMemLen=memPos+256; qsoundMemLen=memPos+256;
@ -1890,7 +1764,6 @@ bool DivEngine::addSampleFromFile(const char* path) {
if (sf_readf_short(f,buf,si.frames)!=si.frames) { if (sf_readf_short(f,buf,si.frames)!=si.frames) {
logW("sample read size mismatch!\n"); logW("sample read size mismatch!\n");
} }
sf_close(f);
DivSample* sample=new DivSample; DivSample* sample=new DivSample;
int sampleCount=(int)song.sample.size(); int sampleCount=(int)song.sample.size();
const char* sName=strrchr(path,DIR_SEPARATOR); const char* sName=strrchr(path,DIR_SEPARATOR);
@ -1902,28 +1775,50 @@ bool DivEngine::addSampleFromFile(const char* path) {
sample->name=sName; sample->name=sName;
int index=0; int index=0;
sample->length=si.frames; if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) {
sample->data=new short[si.frames]; sample->depth=8;
sample->depth=16; } else {
sample->vol=50; sample->depth=16;
sample->pitch=5; }
sample->init(si.frames);
for (int i=0; i<si.frames*si.channels; i+=si.channels) { for (int i=0; i<si.frames*si.channels; i+=si.channels) {
int averaged=0; int averaged=0;
for (int j=0; j<si.channels; j++) { for (int j=0; j<si.channels; j++) {
if (((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8)) { averaged+=buf[i+j];
averaged+=buf[i+j];
} else {
averaged+=buf[i+j];
}
} }
averaged/=si.channels; averaged/=si.channels;
sample->data[index++]=averaged; if (((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8)) {
sample->data8[index++]=averaged;
} else {
sample->data16[index++]=averaged;
}
} }
delete[] buf; delete[] buf;
sample->rate=si.samplerate; sample->rate=si.samplerate;
if (sample->rate<4000) sample->rate=4000; if (sample->rate<4000) sample->rate=4000;
if (sample->rate>96000) sample->rate=96000; if (sample->rate>96000) sample->rate=96000;
sample->centerRate=si.samplerate;
SF_INSTRUMENT inst;
if (sf_command(f, SFC_GET_INSTRUMENT, &inst, sizeof(inst)) == SF_TRUE)
{
// There's no documentation on libsndfile detune range, but the code
// implies -50..50. Yet when loading a file you can get a >50 value.
if(inst.detune > 50)
inst.detune = inst.detune - 100;
short pitch = ((0x3c-inst.basenote)*100) + inst.detune;
sample->centerRate=si.samplerate*pow(2.0,pitch/(12.0 * 100.0));
if(inst.loop_count && inst.loops[0].mode == SF_LOOP_FORWARD)
{
sample->loopStart=inst.loops[0].start;
if(inst.loops[0].end < (unsigned int)sampleCount)
sampleCount=inst.loops[0].end;
}
}
if (sample->centerRate<4000) sample->centerRate=4000;
if (sample->centerRate>64000) sample->centerRate=64000;
sf_close(f);
song.sample.push_back(sample); song.sample.push_back(sample);
song.sampleLen=sampleCount+1; song.sampleLen=sampleCount+1;
renderSamples(); renderSamples();

View file

@ -37,8 +37,8 @@
warnings+=(String("\n")+x); \ warnings+=(String("\n")+x); \
} }
#define DIV_VERSION "dev57" #define DIV_VERSION "dev59"
#define DIV_ENGINE_VERSION 57 #define DIV_ENGINE_VERSION 59
enum DivStatusView { enum DivStatusView {
DIV_STATUS_NOTHING=0, DIV_STATUS_NOTHING=0,
@ -223,8 +223,6 @@ class DivEngine {
size_t totalProcessed; size_t totalProcessed;
private: int* jediTable;
DivSystem systemFromFile(unsigned char val); DivSystem systemFromFile(unsigned char val);
unsigned char systemToFile(DivSystem val); unsigned char systemToFile(DivSystem val);
int dispatchCmd(DivCommand c); int dispatchCmd(DivCommand c);
@ -623,12 +621,16 @@ class DivEngine {
// terminate the engine. // terminate the engine.
bool quit(); bool quit();
unsigned char* adpcmMem; unsigned char* adpcmAMem;
size_t adpcmMemLen; size_t adpcmAMemLen;
unsigned char* adpcmBMem; unsigned char* adpcmBMem;
size_t adpcmBMemLen; size_t adpcmBMemLen;
unsigned char* qsoundMem; unsigned char* qsoundMem;
size_t qsoundMemLen; size_t qsoundMemLen;
unsigned char* qsoundAMem;
size_t qsoundAMemLen;
unsigned char* dpcmMem;
size_t dpcmMemLen;
DivEngine(): DivEngine():
output(NULL), output(NULL),
@ -680,14 +682,17 @@ class DivEngine {
metroPos(0), metroPos(0),
metroAmp(0.0f), metroAmp(0.0f),
totalProcessed(0), totalProcessed(0),
jediTable(NULL),
oscBuf{NULL,NULL}, oscBuf{NULL,NULL},
oscSize(1), oscSize(1),
adpcmMem(NULL), adpcmAMem(NULL),
adpcmMemLen(0), adpcmAMemLen(0),
adpcmBMem(NULL), adpcmBMem(NULL),
adpcmBMemLen(0), adpcmBMemLen(0),
qsoundMem(NULL), qsoundMem(NULL),
qsoundMemLen(0) {} qsoundMemLen(0),
qsoundAMem(NULL),
qsoundAMemLen(0),
dpcmMem(NULL),
dpcmMemLen(0) {}
}; };
#endif #endif

View file

@ -42,6 +42,12 @@ struct InflateBlock {
} }
}; };
static double samplePitches[11]={
0.1666666666, 0.2, 0.25, 0.333333333, 0.5,
1,
2, 3, 4, 5, 6
};
bool DivEngine::loadDMF(unsigned char* file, size_t len) { bool DivEngine::loadDMF(unsigned char* file, size_t len) {
SafeReader reader=SafeReader(file,len); SafeReader reader=SafeReader(file,len);
warnings=""; warnings="";
@ -219,6 +225,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
addWarning("Master System FM expansion is not emulated yet. wait for 0.6!"); addWarning("Master System FM expansion is not emulated yet. wait for 0.6!");
} }
if (ds.system[0]==DIV_SYSTEM_NES_VRC7) {
addWarning("Konami VRC7 is not emulated yet. wait for 0.6!");
}
logI("reading pattern matrix (%d)...\n",ds.ordersLen); logI("reading pattern matrix (%d)...\n",ds.ordersLen);
for (int i=0; i<getChannelCount(ds.system[0]); i++) { for (int i=0; i<getChannelCount(ds.system[0]); i++) {
for (int j=0; j<ds.ordersLen; j++) { for (int j=0; j<ds.ordersLen; j++) {
@ -265,7 +275,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ins->type=DIV_INS_PCE; ins->type=DIV_INS_PCE;
ins->std.volMacroHeight=31; ins->std.volMacroHeight=31;
} }
if (ds.system[0]==DIV_SYSTEM_SMS_OPLL) { if ((ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) && ins->type==DIV_INS_FM) {
ins->type=DIV_INS_OPLL; ins->type=DIV_INS_OPLL;
} }
@ -320,7 +330,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ins->fm.op[j].vib=reader.readC(); ins->fm.op[j].vib=reader.readC();
ins->fm.op[j].ws=reader.readC(); ins->fm.op[j].ws=reader.readC();
} else { } else {
if (ds.system[0]==DIV_SYSTEM_SMS_OPLL) { if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) {
if (j==0) { if (j==0) {
ins->fm.opllPreset=reader.readC(); ins->fm.opllPreset=reader.readC();
} else { } else {
@ -331,7 +341,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
} }
} }
if (ds.version>0x03) { if (ds.version>0x03) {
if (ds.system[0]==DIV_SYSTEM_SMS_OPLL) { if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) {
ins->fm.op[j].ksr=reader.readC(); ins->fm.op[j].ksr=reader.readC();
ins->fm.op[j].vib=reader.readC(); ins->fm.op[j].vib=reader.readC();
ins->fm.op[j].ksl=reader.readC(); ins->fm.op[j].ksl=reader.readC();
@ -603,9 +613,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
} }
for (int i=0; i<ds.sampleLen; i++) { for (int i=0; i<ds.sampleLen; i++) {
DivSample* sample=new DivSample; DivSample* sample=new DivSample;
sample->length=reader.readI(); int length=reader.readI();
if (sample->length<0) { int pitch=5;
logE("invalid sample length %d. are we doing something wrong?\n",sample->length); int vol=50;
short* data;
if (length<0) {
logE("invalid sample length %d. are we doing something wrong?\n",length);
lastError="file is corrupt or unreadable at samples"; lastError="file is corrupt or unreadable at samples";
delete[] file; delete[] file;
return false; return false;
@ -615,30 +628,57 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
} else { } else {
sample->name=""; sample->name="";
} }
logD("%d name %s (%d)\n",i,sample->name.c_str(),sample->length); logD("%d name %s (%d)\n",i,sample->name.c_str(),length);
if (ds.version<0x0b) { sample->rate=22050;
sample->rate=22050; if (ds.version>=0x0b) {
sample->pitch=0;
sample->vol=0;
} else {
sample->rate=fileToDivRate(reader.readC()); sample->rate=fileToDivRate(reader.readC());
sample->pitch=reader.readC(); pitch=reader.readC();
sample->vol=reader.readC(); vol=reader.readC();
} }
if (ds.version>0x15) { if (ds.version>0x15) {
sample->depth=reader.readC(); sample->depth=reader.readC();
if (sample->depth!=8 && sample->depth!=16) {
logW("%d: sample depth is wrong! (%d)\n",i,sample->depth);
sample->depth=16;
}
} else { } else {
sample->depth=16; sample->depth=16;
} }
if (sample->length>0) { if (length>0) {
if (ds.version<0x0b) { if (ds.version<0x0b) {
sample->data=new short[1+(sample->length/2)]; data=new short[1+(length/2)];
reader.read(sample->data,sample->length); reader.read(data,length);
sample->length/=2; length/=2;
} else { } else {
sample->data=new short[sample->length]; data=new short[length];
reader.read(sample->data,sample->length*2); reader.read(data,length*2);
} }
if (pitch!=5) {
logD("%d: scaling from %d...\n",i,pitch);
}
// render data
if (!sample->init((double)length/samplePitches[pitch])) {
logE("%d: error while initializing sample!\n",i);
}
unsigned int k=0;
float mult=(float)(vol)/50.0f;
for (double j=0; j<length; j+=samplePitches[pitch]) {
if (k>=sample->samples) {
break;
}
if (sample->depth==8) {
float next=(float)(data[(unsigned int)j]-0x80)*mult;
sample->data8[k++]=fmin(fmax(next,-128),127);
} else {
float next=(float)data[(unsigned int)j]*mult;
sample->data16[k++]=fmin(fmax(next,-32768),32767);
}
}
delete[] data;
} }
ds.sample.push_back(sample); ds.sample.push_back(sample);
} }
@ -672,6 +712,11 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.system[0]=DIV_SYSTEM_SMS; ds.system[0]=DIV_SYSTEM_SMS;
ds.system[1]=DIV_SYSTEM_OPLL; ds.system[1]=DIV_SYSTEM_OPLL;
} }
if (ds.system[0]==DIV_SYSTEM_NES_VRC7) {
ds.systemLen=2;
ds.system[0]=DIV_SYSTEM_NES;
ds.system[1]=DIV_SYSTEM_VRC7;
}
if (active) quitDispatch(); if (active) quitDispatch();
isBusy.lock(); isBusy.lock();
@ -796,7 +841,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (tchans>DIV_MAX_CHANS) tchans=DIV_MAX_CHANS; if (tchans>DIV_MAX_CHANS) tchans=DIV_MAX_CHANS;
// system volume // system volume
for (int i=0; i<32; i++) ds.systemVol[i]=reader.readC(); for (int i=0; i<32; i++) {
ds.systemVol[i]=reader.readC();
if (ds.version<59 && ds.system[i]==DIV_SYSTEM_NES) {
ds.systemVol[i]/=4;
}
}
// system panning // system panning
for (int i=0; i<32; i++) ds.systemPan[i]=reader.readC(); for (int i=0; i<32; i++) ds.systemPan[i]=reader.readC();
@ -954,6 +1004,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
ds.notes=reader.readString(); ds.notes=reader.readString();
} }
if (ds.version>=59) {
ds.masterVol=reader.readF();
} else {
ds.masterVol=2.0f;
}
// read instruments // read instruments
for (int i=0; i<ds.insLen; i++) { for (int i=0; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument; DivInstrument* ins=new DivInstrument;
@ -986,6 +1042,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
// read samples // read samples
for (int i=0; i<ds.sampleLen; i++) { for (int i=0; i<ds.sampleLen; i++) {
int vol=0;
int pitch=0;
reader.seek(samplePtr[i],SEEK_SET); reader.seek(samplePtr[i],SEEK_SET);
reader.read(magic,4); reader.read(magic,4);
if (strcmp(magic,"SMPL")!=0) { if (strcmp(magic,"SMPL")!=0) {
@ -998,10 +1057,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
DivSample* sample=new DivSample; DivSample* sample=new DivSample;
sample->name=reader.readString(); sample->name=reader.readString();
sample->length=reader.readI(); sample->samples=reader.readI();
sample->rate=reader.readI(); sample->rate=reader.readI();
sample->vol=reader.readS(); if (ds.version<58) {
sample->pitch=reader.readS(); vol=reader.readS();
pitch=reader.readS();
} else {
reader.readI();
}
sample->depth=reader.readC(); sample->depth=reader.readC();
// reserved // reserved
@ -1020,8 +1083,42 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
reader.readI(); reader.readI();
} }
sample->data=new short[sample->length]; if (ds.version>=58) { // modern sample
reader.read(sample->data,2*sample->length); sample->init(sample->samples);
reader.read(sample->getCurBuf(),sample->getCurBufLen());
} else { // legacy sample
int length=sample->samples;
short* data=new short[length];
reader.read(data,2*length);
if (pitch!=5) {
logD("%d: scaling from %d...\n",i,pitch);
}
// render data
if (sample->depth!=8 && sample->depth!=16) {
logW("%d: sample depth is wrong! (%d)\n",i,sample->depth);
sample->depth=16;
}
sample->init(sample->samples);
unsigned int k=0;
float mult=(float)(vol)/50.0f;
for (double j=0; j<length; j+=samplePitches[pitch]) {
if (k>=sample->samples) {
break;
}
if (sample->depth==8) {
float next=(float)(data[(unsigned int)j]-0x80)*mult;
sample->data8[k++]=fmin(fmax(next,-128),127);
} else {
float next=(float)data[(unsigned int)j]*mult;
sample->data16[k++]=fmin(fmax(next,-32768),32767);
}
}
delete[] data;
}
ds.sample.push_back(sample); ds.sample.push_back(sample);
} }
@ -1358,6 +1455,8 @@ SafeWriter* DivEngine::saveFur() {
w->writeString(song.notes,false); w->writeString(song.notes,false);
w->writeF(song.masterVol);
/// INSTRUMENT /// INSTRUMENT
for (int i=0; i<song.insLen; i++) { for (int i=0; i<song.insLen; i++) {
DivInstrument* ins=song.ins[i]; DivInstrument* ins=song.ins[i];
@ -1380,16 +1479,15 @@ SafeWriter* DivEngine::saveFur() {
w->writeI(0); w->writeI(0);
w->writeString(sample->name,false); w->writeString(sample->name,false);
w->writeI(sample->length); w->writeI(sample->samples);
w->writeI(sample->rate); w->writeI(sample->rate);
w->writeS(sample->vol); w->writeI(0); // reserved (for now)
w->writeS(sample->pitch);
w->writeC(sample->depth); w->writeC(sample->depth);
w->writeC(0); w->writeC(0);
w->writeS(sample->centerRate); w->writeS(sample->centerRate);
w->writeI(sample->loopStart); w->writeI(sample->loopStart);
w->write(sample->data,sample->length*2); w->write(sample->getCurBuf(),sample->getCurBufLen());
} }
/// PATTERN /// PATTERN
@ -1462,6 +1560,9 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) {
isFlat=true; isFlat=true;
} }
if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) {
isFlat=true;
}
} }
// fail if more than one system // fail if more than one system
if (!isFlat && song.systemLen!=1) { if (!isFlat && song.systemLen!=1) {
@ -1481,6 +1582,12 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
lastError="Master System FM expansion not supported in 1.0/legacy .dmf!"; lastError="Master System FM expansion not supported in 1.0/legacy .dmf!";
return NULL; return NULL;
} }
// fail if the system is NES+VRC7 and version<25
if (version<25 && song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) {
logE("NES + VRC7 not supported in 1.0/legacy .dmf!\n");
lastError="NES + VRC7 not supported in 1.0/legacy .dmf!";
return NULL;
}
// fail if the system is Furnace-exclusive // fail if the system is Furnace-exclusive
if (!isFlat && systemToFile(song.system[0])&0x80) { if (!isFlat && systemToFile(song.system[0])&0x80) {
logE("cannot save Furnace-exclusive system song!\n"); logE("cannot save Furnace-exclusive system song!\n");
@ -1510,6 +1617,9 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
} else if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { } else if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) {
w->writeC(systemToFile(DIV_SYSTEM_SMS_OPLL)); w->writeC(systemToFile(DIV_SYSTEM_SMS_OPLL));
sys=DIV_SYSTEM_SMS_OPLL; sys=DIV_SYSTEM_SMS_OPLL;
} else if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) {
w->writeC(systemToFile(DIV_SYSTEM_NES_VRC7));
sys=DIV_SYSTEM_NES_VRC7;
} else { } else {
w->writeC(systemToFile(song.system[0])); w->writeC(systemToFile(song.system[0]));
sys=song.system[0]; sys=song.system[0];
@ -1590,12 +1700,12 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
w->writeC(op.rr); w->writeC(op.rr);
w->writeC(op.sl); w->writeC(op.sl);
w->writeC(op.tl); w->writeC(op.tl);
if (sys==DIV_SYSTEM_SMS_OPLL && j==0) { if ((sys==DIV_SYSTEM_SMS_OPLL || sys==DIV_SYSTEM_NES_VRC7) && j==0) {
w->writeC(i->fm.opllPreset); w->writeC(i->fm.opllPreset);
} else { } else {
w->writeC(op.dt2); w->writeC(op.dt2);
} }
if (sys==DIV_SYSTEM_SMS_OPLL) { if (sys==DIV_SYSTEM_SMS_OPLL || sys==DIV_SYSTEM_NES_VRC7) {
w->writeC(op.ksr); w->writeC(op.ksr);
w->writeC(op.vib); w->writeC(op.vib);
w->writeC(op.ksl); w->writeC(op.ksl);
@ -1706,13 +1816,14 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
w->writeC(song.sample.size()); w->writeC(song.sample.size());
for (DivSample* i: song.sample) { for (DivSample* i: song.sample) {
w->writeI(i->length); w->writeI(i->samples);
w->writeString(i->name,true); w->writeString(i->name,true);
w->writeC(divToFileRate(i->rate)); w->writeC(divToFileRate(i->rate));
w->writeC(i->pitch); w->writeC(5);
w->writeC(i->vol); w->writeC(50);
w->writeC(i->depth); // i'm too lazy to deal with .dmf's weird way of storing 8-bit samples
w->write(i->data,2*i->length); w->writeC(16);
w->write(i->data16,i->length16);
} }
return w; return w;

View file

@ -72,14 +72,10 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le
chan[i].audSub-=AMIGA_DIVIDER; chan[i].audSub-=AMIGA_DIVIDER;
if (chan[i].audSub<0) { if (chan[i].audSub<0) {
DivSample* s=parent->song.sample[chan[i].sample]; DivSample* s=parent->song.sample[chan[i].sample];
if (s->rendLength>0) { if (s->samples>0) {
if (s->depth==8) { chan[i].audDat=s->data8[chan[i].audPos++];
chan[i].audDat=s->rendData[chan[i].audPos++]; if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) {
} else { if (s->loopStart>=0 && s->loopStart<=(int)s->samples) {
chan[i].audDat=s->rendData[chan[i].audPos++]>>8;
}
if (chan[i].audPos>=s->rendLength || chan[i].audPos>=131071) {
if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) {
chan[i].audPos=s->loopStart; chan[i].audPos=s->loopStart;
} else { } else {
chan[i].sample=-1; chan[i].sample=-1;

View file

@ -90,16 +90,12 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
dacPeriod-=6; dacPeriod-=6;
if (dacPeriod<1) { if (dacPeriod<1) {
DivSample* s=parent->song.sample[dacSample]; DivSample* s=parent->song.sample[dacSample];
if (s->rendLength>0) { if (s->samples>0) {
if (!isMuted[5]) { if (!isMuted[5]) {
if (s->depth==8) { immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
immWrite(0x2a,(unsigned char)s->rendData[dacPos]+0x80);
} else {
immWrite(0x2a,((unsigned short)s->rendData[dacPos]+0x8000)>>8);
}
} }
if (++dacPos>=s->rendLength) { if (++dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) { if (s->loopStart>=0 && s->loopStart<=(int)s->samples) {
dacPos=s->loopStart; dacPos=s->loopStart;
} else { } else {
dacSample=-1; dacSample=-1;
@ -164,16 +160,12 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
dacPeriod-=24; dacPeriod-=24;
if (dacPeriod<1) { if (dacPeriod<1) {
DivSample* s=parent->song.sample[dacSample]; DivSample* s=parent->song.sample[dacSample];
if (s->rendLength>0) { if (s->samples>0) {
if (!isMuted[5]) { if (!isMuted[5]) {
if (s->depth==8) { immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80);
immWrite(0x2a,(unsigned char)s->rendData[dacPos]+0x80);
} else {
immWrite(0x2a,((unsigned short)s->rendData[dacPos]+0x8000)>>8);
}
} }
if (++dacPos>=s->rendLength) { if (++dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) { if (s->loopStart>=0 && s->loopStart<=(int)s->samples) {
dacPos=s->loopStart; dacPos=s->loopStart;
} else { } else {
dacSample=-1; dacSample=-1;

View file

@ -30,6 +30,7 @@
#define WRITE_CONTROL(ch,v) rWrite(0x25+(ch<<3),(v)) #define WRITE_CONTROL(ch,v) rWrite(0x25+(ch<<3),(v))
#define WRITE_OTHER(ch,v) rWrite(0x27+(ch<<3),(v)) #define WRITE_OTHER(ch,v) rWrite(0x27+(ch<<3),(v))
#define WRITE_ATTEN(ch,v) rWrite((0x40+ch),(v)) #define WRITE_ATTEN(ch,v) rWrite((0x40+ch),(v))
#define WRITE_STEREO(v) rWrite(0x50,(v))
#define CHIP_DIVIDER 64 #define CHIP_DIVIDER 64
@ -52,7 +53,7 @@ static int bsr(uint16_t v) {
static int bsr(uint16_t v) static int bsr(uint16_t v)
{ {
if (v) { if (v) {
return 16 - __builtin_clz(v); return 32 - __builtin_clz(v);
} }
else{ else{
return -1; return -1;
@ -64,7 +65,7 @@ static int bsr(uint16_t v)
static int bsr(uint16_t v) static int bsr(uint16_t v)
{ {
uint16_t mask = 0x8000; uint16_t mask = 0x8000;
for (int i = 31; i >= 0; --i) { for (int i = 15; i >= 0; --i) {
if (v&mask) if (v&mask)
return (int)i; return (int)i;
mask>>=1; mask>>=1;
@ -88,9 +89,9 @@ const char* regCheatSheetLynx[]={
const char** DivPlatformLynx::getRegisterSheet() { const char** DivPlatformLynx::getRegisterSheet() {
return regCheatSheetLynx; return regCheatSheetLynx;
} }
const char* DivPlatformLynx::getEffectName(unsigned char effect) { const char* DivPlatformLynx::getEffectName(unsigned char effect) {
switch (effect) switch (effect)
{ {
case 0x30: case 0x31: case 0x32: case 0x33: case 0x30: case 0x31: case 0x32: case 0x33:
@ -197,7 +198,8 @@ int DivPlatformLynx::dispatch(DivCommand c) {
} }
break; break;
case DIV_CMD_PANNING: case DIV_CMD_PANNING:
WRITE_ATTEN(c.chan, c.value); chan[c.chan].pan=((c.value&0x0f)<<4)|((c.value&0xf0)>>4);
WRITE_ATTEN(c.chan,chan[c.chan].pan);
break; break;
case DIV_CMD_GET_VOLUME: case DIV_CMD_GET_VOLUME:
if (chan[c.chan].std.hasVol) { if (chan[c.chan].std.hasVol) {
@ -261,6 +263,10 @@ void DivPlatformLynx::muteChannel(int ch, bool mute) {
if (chan[ch].active) WRITE_VOLUME(ch,(isMuted[ch]?0:(chan[ch].outVol&127))); if (chan[ch].active) WRITE_VOLUME(ch,(isMuted[ch]?0:(chan[ch].outVol&127)));
} }
bool DivPlatformLynx::isStereo() {
return true;
}
void DivPlatformLynx::forceIns() { void DivPlatformLynx::forceIns() {
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
if (chan[i].active) { if (chan[i].active) {
@ -272,16 +278,16 @@ void DivPlatformLynx::forceIns() {
void* DivPlatformLynx::getChanState(int ch) { void* DivPlatformLynx::getChanState(int ch) {
return &chan[ch]; return &chan[ch];
} }
unsigned char* DivPlatformLynx::getRegisterPool() unsigned char* DivPlatformLynx::getRegisterPool()
{ {
return const_cast<unsigned char*>( mikey->getRegisterPool() ); return const_cast<unsigned char*>( mikey->getRegisterPool() );
} }
int DivPlatformLynx::getRegisterPoolSize() int DivPlatformLynx::getRegisterPoolSize()
{ {
return 4*8+4; return 4*8+4;
} }
void DivPlatformLynx::reset() { void DivPlatformLynx::reset() {
@ -294,6 +300,7 @@ void DivPlatformLynx::reset() {
if (dumpWrites) { if (dumpWrites) {
addWrite(0xffffffff,0); addWrite(0xffffffff,0);
} }
WRITE_STEREO(0);
} }
bool DivPlatformLynx::keyOffAffectsArp(int ch) { bool DivPlatformLynx::keyOffAffectsArp(int ch) {

View file

@ -45,7 +45,7 @@ class DivPlatformLynx: public DivDispatch {
MikeyFreqDiv fd; MikeyFreqDiv fd;
MikeyDuty duty; MikeyDuty duty;
int baseFreq, pitch, note, actualNote, lfsr; int baseFreq, pitch, note, actualNote, lfsr;
unsigned char ins; unsigned char ins, pan;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
signed char vol, outVol; signed char vol, outVol;
Channel(): Channel():
@ -58,6 +58,7 @@ class DivPlatformLynx: public DivDispatch {
actualNote(0), actualNote(0),
lfsr(-1), lfsr(-1),
ins(-1), ins(-1),
pan(0xff),
active(false), active(false),
insChanged(true), insChanged(true),
freqChanged(false), freqChanged(false),
@ -81,6 +82,7 @@ class DivPlatformLynx: public DivDispatch {
void forceIns(); void forceIns();
void tick(); void tick();
void muteChannel(int ch, bool mute); void muteChannel(int ch, bool mute);
bool isStereo();
bool keyOffAffectsArp(int ch); bool keyOffAffectsArp(int ch);
bool keyOffAffectsPorta(int ch); bool keyOffAffectsPorta(int ch);
//int getPortaFloor(int ch); //int getPortaFloor(int ch);

View file

@ -76,16 +76,12 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len)
dacPeriod+=dacRate; dacPeriod+=dacRate;
if (dacPeriod>=rate) { if (dacPeriod>=rate) {
DivSample* s=parent->song.sample[dacSample]; DivSample* s=parent->song.sample[dacSample];
if (s->rendLength>0) { if (s->samples>0) {
if (!isMuted[4]) { if (!isMuted[4]) {
if (s->depth==8) { rWrite(0x4011,((unsigned char)s->data8[dacPos]+0x80)>>1);
rWrite(0x4011,((unsigned char)s->rendData[dacPos]+0x80)>>1);
} else {
rWrite(0x4011,((unsigned short)s->rendData[dacPos]+0x8000)>>9);
}
} }
if (++dacPos>=s->rendLength) { if (++dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) { if (s->loopStart>=0 && s->loopStart<=(int)s->samples) {
dacPos=s->loopStart; dacPos=s->loopStart;
} else { } else {
dacSample=-1; dacSample=-1;
@ -103,7 +99,10 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len)
if (nes->apu.clocked) { if (nes->apu.clocked) {
nes->apu.clocked=false; nes->apu.clocked=false;
} }
bufL[i]=(pulse_output(nes)+tnd_output(nes))*30; int sample=(pulse_output(nes)+tnd_output(nes)-128)<<7;
if (sample>32767) sample=32767;
if (sample<-32768) sample=-32768;
bufL[i]=sample;
} }
} }

View file

@ -82,21 +82,16 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
chan[i].dacPeriod+=chan[i].dacRate; chan[i].dacPeriod+=chan[i].dacRate;
if (chan[i].dacPeriod>rate) { if (chan[i].dacPeriod>rate) {
DivSample* s=parent->song.sample[chan[i].dacSample]; DivSample* s=parent->song.sample[chan[i].dacSample];
if (s->rendLength<=0) { if (s->samples<=0) {
chan[i].dacSample=-1; chan[i].dacSample=-1;
continue; continue;
} }
chWrite(i,0x07,0); chWrite(i,0x07,0);
if (s->depth==8) { chWrite(i,0x04,0xdf);
chWrite(i,0x04,0xdf); chWrite(i,0x06,(((unsigned char)s->data8[chan[i].dacPos]+0x80)>>3));
chWrite(i,0x06,(((unsigned char)s->rendData[chan[i].dacPos]+0x80)>>3));
} else {
chWrite(i,0x04,0xdf);
chWrite(i,0x06,(((unsigned short)s->rendData[chan[i].dacPos]+0x8000)>>11));
}
chan[i].dacPos++; chan[i].dacPos++;
if (chan[i].dacPos>=s->rendLength) { if (chan[i].dacPos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) { if (s->loopStart>=0 && s->loopStart<=(int)s->samples) {
chan[i].dacPos=s->loopStart; chan[i].dacPos=s->loopStart;
} else { } else {
chan[i].dacSample=-1; chan[i].dacSample=-1;

View file

@ -296,18 +296,18 @@ void DivPlatformQSound::tick() {
} else { } else {
off=(double)s->centerRate/24038.0/16.0; off=(double)s->centerRate/24038.0/16.0;
} }
qsound_bank = 0x8000 | (s->rendOffQsound >> 16); qsound_bank = 0x8000 | (s->offQSound >> 16);
qsound_addr = s->rendOffQsound & 0xffff; qsound_addr = s->offQSound & 0xffff;
int length = s->length; int length = s->samples;
if (length > 65536 - 16) { if (length > 65536 - 16) {
length = 65536 - 16; length = 65536 - 16;
} }
if (s->loopStart == -1 || s->loopStart >= length) { if (s->loopStart == -1 || s->loopStart >= length) {
qsound_end = s->rendOffQsound + length + 15; qsound_end = s->offQSound + length + 15;
qsound_loop = 15; qsound_loop = 15;
} else { } else {
qsound_end = s->rendOffQsound + length; qsound_end = s->offQSound + length;
qsound_loop = length - s->loopStart; qsound_loop = length - s->loopStart;
} }
} }

View file

@ -44,22 +44,17 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t
for (int i=0; i<16; i++) { for (int i=0; i<16; i++) {
if (chan[i].pcm.sample>=0 && chan[i].pcm.sample<parent->song.sampleLen) { if (chan[i].pcm.sample>=0 && chan[i].pcm.sample<parent->song.sampleLen) {
DivSample* s=parent->song.sample[chan[i].pcm.sample]; DivSample* s=parent->song.sample[chan[i].pcm.sample];
if (s->rendLength<=0) { if (s->samples<=0) {
chan[i].pcm.sample=-1; chan[i].pcm.sample=-1;
continue; continue;
} }
if (!isMuted[i]) { if (!isMuted[i]) {
if (s->depth==8) { pcmL+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolL);
pcmL+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolL); pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR);
pcmR+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolR);
} else {
pcmL+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolL)>>8;
pcmR+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolR)>>8;
}
} }
chan[i].pcm.pos+=chan[i].pcm.freq; chan[i].pcm.pos+=chan[i].pcm.freq;
if (chan[i].pcm.pos>=(s->rendLength<<8)) { if (chan[i].pcm.pos>=(s->samples<<8)) {
if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) { if (s->loopStart>=0 && s->loopStart<=(int)s->samples) {
chan[i].pcm.pos=s->loopStart<<8; chan[i].pcm.pos=s->loopStart<<8;
} else { } else {
chan[i].pcm.sample=-1; chan[i].pcm.sample=-1;
@ -152,17 +147,17 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
chan[c.chan].furnacePCM=true; chan[c.chan].furnacePCM=true;
if (dumpWrites) { // Sega PCM writes if (dumpWrites) { // Sega PCM writes
DivSample* s=parent->song.sample[chan[c.chan].pcm.sample]; DivSample* s=parent->song.sample[chan[c.chan].pcm.sample];
addWrite(0x10086+(c.chan<<3),3+((s->rendOffP>>16)<<3)); addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3));
addWrite(0x10084+(c.chan<<3),(s->rendOffP)&0xff); addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
addWrite(0x10085+(c.chan<<3),(s->rendOffP>>8)&0xff); addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff);
addWrite(0x10006+(c.chan<<3),MIN(255,((s->rendOffP&0xffff)+s->rendLength-1)>>8)); addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+s->length8-1)>>8));
if (s->loopStart<0 || s->loopStart>=(int)s->rendLength) { if (s->loopStart<0 || s->loopStart>=(int)s->length8) {
addWrite(0x10086+(c.chan<<3),2+((s->rendOffP>>16)<<3)); addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3));
} else { } else {
int loopPos=(s->rendOffP&0xffff)+s->loopStart+s->loopOffP; int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP;
addWrite(0x10004+(c.chan<<3),loopPos&0xff); addWrite(0x10004+(c.chan<<3),loopPos&0xff);
addWrite(0x10005+(c.chan<<3),(loopPos>>8)&0xff); addWrite(0x10005+(c.chan<<3),(loopPos>>8)&0xff);
addWrite(0x10086+(c.chan<<3),((s->rendOffP>>16)<<3)); addWrite(0x10086+(c.chan<<3),((s->offSegaPCM>>16)<<3));
} }
} }
} else { } else {
@ -182,17 +177,17 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
chan[c.chan].furnacePCM=false; chan[c.chan].furnacePCM=false;
if (dumpWrites) { // Sega PCM writes if (dumpWrites) { // Sega PCM writes
DivSample* s=parent->song.sample[chan[c.chan].pcm.sample]; DivSample* s=parent->song.sample[chan[c.chan].pcm.sample];
addWrite(0x10086+(c.chan<<3),3+((s->rendOffP>>16)<<3)); addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3));
addWrite(0x10084+(c.chan<<3),(s->rendOffP)&0xff); addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
addWrite(0x10085+(c.chan<<3),(s->rendOffP>>8)&0xff); addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff);
addWrite(0x10006+(c.chan<<3),MIN(255,((s->rendOffP&0xffff)+s->rendLength-1)>>8)); addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+s->length8-1)>>8));
if (s->loopStart<0 || s->loopStart>=(int)s->rendLength) { if (s->loopStart<0 || s->loopStart>=(int)s->length8) {
addWrite(0x10086+(c.chan<<3),2+((s->rendOffP>>16)<<3)); addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3));
} else { } else {
int loopPos=(s->rendOffP&0xffff)+s->loopStart+s->loopOffP; int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP;
addWrite(0x10004+(c.chan<<3),loopPos&0xff); addWrite(0x10004+(c.chan<<3),loopPos&0xff);
addWrite(0x10005+(c.chan<<3),(loopPos>>8)&0xff); addWrite(0x10005+(c.chan<<3),(loopPos>>8)&0xff);
addWrite(0x10086+(c.chan<<3),((s->rendOffP>>16)<<3)); addWrite(0x10086+(c.chan<<3),((s->offSegaPCM>>16)<<3));
} }
addWrite(0x10007+(c.chan<<3),chan[c.chan].pcm.freq); addWrite(0x10007+(c.chan<<3),chan[c.chan].pcm.freq);
} }

View file

@ -301,7 +301,9 @@ void DivPlatformSMS::poke(std::vector<DivRegWrite>& wlist) {
} }
void DivPlatformSMS::setFlags(unsigned int flags) { void DivPlatformSMS::setFlags(unsigned int flags) {
if ((flags&3)==2) { if ((flags&3)==3) {
chipClock=COLOR_NTSC/2.0;
} else if ((flags&3)==2) {
chipClock=4000000; chipClock=4000000;
} else if ((flags&3)==1) { } else if ((flags&3)==1) {
chipClock=COLOR_PAL*4.0/5.0; chipClock=COLOR_PAL*4.0/5.0;

View file

@ -162,7 +162,7 @@ void qsound_reset(struct qsound_chip *chip)
chip->state_counter = 0; chip->state_counter = 0;
} }
uint8_t qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int samples) void qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int samples)
{ {
// Clear the buffers // Clear the buffers
memset(outputs[0], 0, samples * sizeof(*outputs[0])); memset(outputs[0], 0, samples * sizeof(*outputs[0]));
@ -174,10 +174,9 @@ uint8_t qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int sa
outputs[0][i] = chip->out[0]; outputs[0][i] = chip->out[0];
outputs[1][i] = chip->out[1]; outputs[1][i] = chip->out[1];
} }
return 0;
} }
uint8_t qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data) void qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data)
{ {
switch (offset) switch (offset)
{ {
@ -193,7 +192,6 @@ uint8_t qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data)
default: default:
break; break;
} }
return 0;
} }
uint8_t qsound_r(struct qsound_chip *chip) uint8_t qsound_r(struct qsound_chip *chip)
@ -614,10 +612,14 @@ static void state_normal_update(struct qsound_chip *chip)
pan_index = 97; pan_index = 97;
// Apply different volume tables on the dry and wet inputs. // Apply different volume tables on the dry and wet inputs.
dry -= (chip->voice_output[v] * chip->pan_tables[ch][PANTBL_DRY][pan_index])<<2; dry -= (chip->voice_output[v] * chip->pan_tables[ch][PANTBL_DRY][pan_index]);
wet -= (chip->voice_output[v] * chip->pan_tables[ch][PANTBL_WET][pan_index])<<2; wet -= (chip->voice_output[v] * chip->pan_tables[ch][PANTBL_WET][pan_index]);
} }
// Saturate accumulated voices
dry = CLAMP(dry, -0x1fffffff, 0x1fffffff) << 2;
wet = CLAMP(wet, -0x1fffffff, 0x1fffffff) << 2;
// Apply FIR filter on 'wet' input // Apply FIR filter on 'wet' input
wet = fir(&chip->filter[ch], wet >> 16); wet = fir(&chip->filter[ch], wet >> 16);

View file

@ -109,8 +109,8 @@ long qsound_start(struct qsound_chip *chip, int clock);
void qsound_reset(struct qsound_chip *chip); void qsound_reset(struct qsound_chip *chip);
void qsound_update(struct qsound_chip *chip); void qsound_update(struct qsound_chip *chip);
uint8_t qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int samples); void qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int samples);
uint8_t qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data); void qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data);
uint8_t qsound_r(struct qsound_chip *chip); uint8_t qsound_r(struct qsound_chip *chip);
void qsound_write_data(struct qsound_chip *chip, uint8_t address, uint16_t data); void qsound_write_data(struct qsound_chip *chip, uint8_t address, uint16_t data);
uint16_t qsound_read_data(struct qsound_chip *chip, uint8_t address); uint16_t qsound_read_data(struct qsound_chip *chip, uint8_t address);

View file

@ -437,9 +437,9 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
break; break;
} }
DivSample* s=parent->song.sample[12*sampleBank+c.value%12]; DivSample* s=parent->song.sample[12*sampleBank+c.value%12];
immWrite(0x110+c.chan-7,(s->rendOff>>8)&0xff); immWrite(0x110+c.chan-7,(s->offA>>8)&0xff);
immWrite(0x118+c.chan-7,s->rendOff>>16); immWrite(0x118+c.chan-7,s->offA>>16);
int end=s->rendOff+s->adpcmRendLength-1; int end=s->offA+s->lengthA-1;
immWrite(0x120+c.chan-7,(end>>8)&0xff); immWrite(0x120+c.chan-7,(end>>8)&0xff);
immWrite(0x128+c.chan-7,end>>16); immWrite(0x128+c.chan-7,end>>16);
immWrite(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); immWrite(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol));

View file

@ -22,12 +22,20 @@
#include "../engine.h" #include "../engine.h"
uint8_t DivYM2610Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) { uint8_t DivYM2610Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) {
//printf("wants to read from %x\n",address); switch (type) {
if (type!=ymfm::ACCESS_ADPCM_A) return 0; case ymfm::ACCESS_ADPCM_A:
return parent->adpcmMem[address&0xffffff]; if (parent->adpcmAMem==NULL) return 0;
/*if (12*sampleBank+(address>>16)>=parent->song.sampleLen) return 0; if ((address&0xffffff)>=parent->adpcmAMemLen) return 0;
return parent->song.sample[12*sampleBank+(address>>16)]->adpcmRendData[(address&0xffff)];*/ return parent->adpcmAMem[address&0xffffff];
case ymfm::ACCESS_ADPCM_B:
if (parent->adpcmBMem==NULL) return 0;
if ((address&0xffffff)>=parent->adpcmBMemLen) return 0;
return parent->adpcmBMem[address&0xffffff];
default:
return 0;
}
return 0;
} }
void DivYM2610Interface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) { void DivYM2610Interface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) {
} }

View file

@ -1132,24 +1132,24 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
DivSample* s=song.sample[sPreview.sample]; DivSample* s=song.sample[sPreview.sample];
for (size_t i=0; i<prevtotal; i++) { for (size_t i=0; i<prevtotal; i++) {
if (sPreview.pos>=s->rendLength) { if (sPreview.pos>=s->samples) {
samp_temp=0; samp_temp=0;
} else { } else {
samp_temp=s->rendData[sPreview.pos++]; samp_temp=s->data16[sPreview.pos++];
} }
if (s->depth==8) samp_temp<<=8; if (s->depth==8) samp_temp<<=8;
blip_add_delta(samp_bb,i,samp_temp-samp_prevSample); blip_add_delta(samp_bb,i,samp_temp-samp_prevSample);
samp_prevSample=samp_temp; samp_prevSample=samp_temp;
if (sPreview.pos>=s->rendLength) { if (sPreview.pos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<(int)s->rendLength) { if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
sPreview.pos=s->loopStart; sPreview.pos=s->loopStart;
} }
} }
} }
if (sPreview.pos>=s->rendLength) { if (sPreview.pos>=s->samples) {
if (s->loopStart>=0 && s->loopStart<(int)s->rendLength) { if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
sPreview.pos=s->loopStart; sPreview.pos=s->loopStart;
} else { } else {
sPreview.sample=-1; sPreview.sample=-1;
@ -1294,17 +1294,17 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
} }
for (int i=0; i<song.systemLen; i++) { for (int i=0; i<song.systemLen; i++) {
float volL=((float)song.systemVol[i]/64.0f)*((float)MIN(127,127-(int)song.systemPan[i])/127.0f); float volL=((float)song.systemVol[i]/64.0f)*((float)MIN(127,127-(int)song.systemPan[i])/127.0f)*song.masterVol;
float volR=((float)song.systemVol[i]/64.0f)*((float)MIN(127,127+(int)song.systemPan[i])/127.0f); float volR=((float)song.systemVol[i]/64.0f)*((float)MIN(127,127+(int)song.systemPan[i])/127.0f)*song.masterVol;
if (disCont[i].dispatch->isStereo()) { if (disCont[i].dispatch->isStereo()) {
for (size_t j=0; j<size; j++) { for (size_t j=0; j<size; j++) {
out[0][j]+=((float)disCont[i].bbOut[0][j]/16384.0)*volL; out[0][j]+=((float)disCont[i].bbOut[0][j]/32768.0)*volL;
out[1][j]+=((float)disCont[i].bbOut[1][j]/16384.0)*volR; out[1][j]+=((float)disCont[i].bbOut[1][j]/32768.0)*volR;
} }
} else { } else {
for (size_t j=0; j<size; j++) { for (size_t j=0; j<size; j++) {
out[0][j]+=((float)disCont[i].bbOut[0][j]/16384.0)*volL; out[0][j]+=((float)disCont[i].bbOut[0][j]/32768.0)*volL;
out[1][j]+=((float)disCont[i].bbOut[0][j]/16384.0)*volR; out[1][j]+=((float)disCont[i].bbOut[0][j]/32768.0)*volR;
} }
} }
} }

View file

@ -21,21 +21,26 @@
#include "../ta-log.h" #include "../ta-log.h"
#include <string.h> #include <string.h>
#include <sndfile.h> #include <sndfile.h>
#include <math.h>
extern "C" {
#include "../../extern/adpcm/bs_codec.h"
#include "../../extern/adpcm/oki_codec.h"
#include "../../extern/adpcm/yma_codec.h"
#include "../../extern/adpcm/ymb_codec.h"
#include "../../extern/adpcm/ymz_codec.h"
}
bool DivSample::save(const char* path) { bool DivSample::save(const char* path) {
SNDFILE* f; SNDFILE* f;
SF_INFO si; SF_INFO si;
memset(&si,0,sizeof(SF_INFO)); memset(&si,0,sizeof(SF_INFO));
if (length<1) return false; if (length16<1) return false;
si.channels=1; si.channels=1;
si.samplerate=rate; si.samplerate=rate;
if (depth==16) { si.format=SF_FORMAT_PCM_16|SF_FORMAT_WAV;
si.format=SF_FORMAT_PCM_16|SF_FORMAT_WAV;
} else {
si.format=SF_FORMAT_PCM_U8|SF_FORMAT_WAV;
}
f=sf_open(path,SFM_WRITE,&si); f=sf_open(path,SFM_WRITE,&si);
@ -44,23 +49,268 @@ bool DivSample::save(const char* path) {
return false; return false;
} }
if (depth==16) { SF_INSTRUMENT inst;
sf_writef_short(f,data,length); memset(&inst, 0, sizeof(inst));
} else { inst.gain = 1;
short* cbuf=new short[length]; short pitch = (0x3c * 100) + 50 - (log2((double)centerRate/rate) * 12.0 * 100.0);
for (int i=0; i<length; i++) { inst.basenote = pitch / 100;
cbuf[i]=(data[i]<<8)^0x8000; inst.detune = 50 - (pitch % 100);
} inst.velocity_hi = 0x7f;
sf_writef_short(f,cbuf,length); inst.key_hi = 0x7f;
delete[] cbuf; if(loopStart != -1)
{
inst.loop_count = 1;
inst.loops[0].mode = SF_LOOP_FORWARD;
inst.loops[0].start = loopStart;
inst.loops[0].end = samples;
} }
sf_command(f, SFC_SET_INSTRUMENT, &inst, sizeof(inst));
sf_write_short(f,data16,length16);
sf_close(f); sf_close(f);
return true; return true;
} }
DivSample::~DivSample() { // 16-bit memory is padded to 512, to make things easier for ADPCM-A/B.
if (data) delete[] data; bool DivSample::initInternal(unsigned char d, int count) {
if (rendData) delete[] rendData; switch (d) {
if (adpcmRendData) delete[] adpcmRendData; case 0: // 1-bit
if (data1!=NULL) delete[] data1;
length1=(count+7)/8;
data1=new unsigned char[length1];
memset(data1,0,length1);
break;
case 1: // DPCM
if (dataDPCM!=NULL) delete[] dataDPCM;
lengthDPCM=(count+7)/8;
dataDPCM=new unsigned char[lengthDPCM];
memset(dataDPCM,0,lengthDPCM);
break;
case 4: // QSound ADPCM
if (dataQSoundA!=NULL) delete[] dataQSoundA;
lengthQSoundA=(count+1)/2;
dataQSoundA=new unsigned char[lengthQSoundA];
memset(dataQSoundA,0,lengthQSoundA);
break;
case 5: // ADPCM-A
if (dataA!=NULL) delete[] dataA;
lengthA=(count+1)/2;
dataA=new unsigned char[(lengthA+255)&(~0xff)];
memset(dataA,0,(lengthA+255)&(~0xff));
break;
case 6: // ADPCM-B
if (dataB!=NULL) delete[] dataB;
lengthB=(count+1)/2;
dataB=new unsigned char[(lengthB+255)&(~0xff)];
memset(dataB,0,(lengthB+255)&(~0xff));
break;
case 7: // X68000 ADPCM
if (dataX68!=NULL) delete[] dataX68;
lengthX68=(count+1)/2;
dataX68=new unsigned char[lengthX68];
memset(dataX68,0,lengthX68);
break;
case 8: // 8-bit
if (data8!=NULL) delete[] data8;
length8=count;
data8=new signed char[length8];
memset(data8,0,length8);
break;
case 9: // BRR
if (dataBRR!=NULL) delete[] dataBRR;
lengthBRR=9*((count+15)/16);
dataBRR=new unsigned char[lengthBRR];
memset(dataBRR,0,lengthBRR);
break;
case 10: // VOX
if (dataVOX!=NULL) delete[] dataVOX;
lengthVOX=(count+1)/2;
dataVOX=new unsigned char[lengthVOX];
memset(dataVOX,0,lengthVOX);
break;
case 16: // 16-bit
if (data16!=NULL) delete[] data16;
length16=count*2;
data16=new short[(count+511)&(~0x1ff)];
memset(data16,0,((count+511)&(~0x1ff))*sizeof(short));
break;
default:
return false;
}
return true;
}
bool DivSample::init(unsigned int count) {
if (!initInternal(depth,count)) return false;
samples=count;
return true;
}
void DivSample::render() {
// step 1: convert to 16-bit if needed
if (depth!=16) {
if (!initInternal(16,samples)) return;
switch (depth) {
case 0: // 1-bit
for (unsigned int i=0; i<samples; i++) {
data16[i]=((data1[i>>3]>>(i&7))&1)?0x7fff:-0x7fff;
}
break;
case 1: { // DPCM
int accum=0;
for (unsigned int i=0; i<samples; i++) {
accum+=((data1[i>>3]>>(i&7))&1)?1:-1;
if (accum>63) accum=63;
if (accum<-64) accum=-64;
data16[i]=accum*512;
}
break;
}
case 4: // QSound ADPCM
bs_decode(dataQSoundA,data16,samples);
break;
case 5: // ADPCM-A
yma_decode(dataA,data16,samples);
break;
case 6: // ADPCM-B
ymb_decode(dataB,data16,samples);
break;
case 7: // X6800 ADPCM
oki6258_decode(dataX68,data16,samples);
break;
case 8: // 8-bit PCM
for (unsigned int i=0; i<samples; i++) {
data16[i]=data8[i]<<8;
}
break;
case 9: // BRR
// TODO!
break;
case 10: // VOX
oki_decode(dataVOX,data16,samples);
break;
default:
return;
}
}
// step 2: render to other formats
if (depth!=0) { // 1-bit
if (!initInternal(0,samples)) return;
for (unsigned int i=0; i<samples; i++) {
if (data16[i]>0) {
data1[i>>3]|=1<<(i&7);
}
}
}
if (depth!=1) { // DPCM
if (!initInternal(1,samples)) return;
int accum=63;
for (unsigned int i=0; i<samples; i++) {
int next=((unsigned short)(data16[i]^0x8000))>>9;
if (next>accum) {
dataDPCM[i>>3]|=1<<(i&7);
accum++;
} else {
accum--;
}
if (accum<0) accum=0;
if (accum>127) accum=127;
}
}
if (depth!=4) { // QSound ADPCM
if (!initInternal(4,samples)) return;
bs_encode(data16,dataQSoundA,samples);
}
// TODO: pad to 256.
if (depth!=5) { // ADPCM-A
if (!initInternal(5,samples)) return;
yma_encode(data16,dataA,(samples+511)&(~0x1ff));
}
if (depth!=6) { // ADPCM-B
if (!initInternal(6,samples)) return;
ymb_encode(data16,dataB,(samples+511)&(~0x1ff));
}
if (depth!=7) { // X68000 ADPCM
if (!initInternal(7,samples)) return;
oki6258_encode(data16,dataX68,samples);
}
if (depth!=8) { // 8-bit PCM
if (!initInternal(8,samples)) return;
for (unsigned int i=0; i<samples; i++) {
data8[i]=data16[i]>>8;
}
}
// TODO: BRR!
if (depth!=10) { // VOX
if (!initInternal(10,samples)) return;
oki_encode(data16,dataVOX,samples);
}
}
void* DivSample::getCurBuf() {
switch (depth) {
case 0:
return data1;
case 1:
return dataDPCM;
case 4:
return dataQSoundA;
case 5:
return dataA;
case 6:
return dataB;
case 7:
return dataX68;
case 8:
return data8;
case 9:
return dataBRR;
case 10:
return dataVOX;
case 16:
return data16;
}
return NULL;
}
unsigned int DivSample::getCurBufLen() {
switch (depth) {
case 0:
return length1;
case 1:
return lengthDPCM;
case 4:
return lengthQSoundA;
case 5:
return lengthA;
case 6:
return lengthB;
case 7:
return lengthX68;
case 8:
return length8;
case 9:
return lengthBRR;
case 10:
return lengthVOX;
case 16:
return length16;
}
return 0;
}
DivSample::~DivSample() {
if (data8) delete[] data8;
if (data16) delete[] data16;
if (data1) delete[] data1;
if (dataDPCM) delete[] dataDPCM;
if (dataQSoundA) delete[] dataQSoundA;
if (dataA) delete[] dataA;
if (dataB) delete[] dataB;
if (dataX68) delete[] dataX68;
if (dataBRR) delete[] dataBRR;
if (dataVOX) delete[] dataVOX;
} }

View file

@ -21,41 +21,83 @@
struct DivSample { struct DivSample {
String name; String name;
int length, rate, centerRate, loopStart, loopOffP; int rate, centerRate, loopStart, loopOffP;
signed char vol, pitch;
// valid values are: // valid values are:
// - 0: ZX Spectrum overlay drum (1-bit PCM) // - 0: ZX Spectrum overlay drum (1-bit)
// - 1: 1-bit NES DPCM // - 1: 1-bit NES DPCM (1-bit)
// - 4: BRR // - 4: QSound ADPCM
// - 5: raw ADPCM-A // - 5: ADPCM-A
// - 6: raw ADPCM-B // - 6: ADPCM-B
// - 7: X68000 ADPCM
// - 8: 8-bit PCM // - 8: 8-bit PCM
// - 9: BRR (SNES)
// - 10: VOX
// - 16: 16-bit PCM // - 16: 16-bit PCM
unsigned char depth; unsigned char depth;
short* data;
unsigned int rendLength, adpcmRendLength, rendOff, rendOffP, rendOffContiguous, rendOffQsound; // these are the new data structures.
short* rendData; signed char* data8; // 8
unsigned char* adpcmRendData; short* data16; // 16
unsigned char* data1; // 0
unsigned char* dataDPCM; // 1
unsigned char* dataQSoundA; // 4
unsigned char* dataA; // 5
unsigned char* dataB; // 6
unsigned char* dataX68; // 7
unsigned char* dataBRR; // 9
unsigned char* dataVOX; // 10
unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX;
unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX;
unsigned int offSegaPCM, offQSound;
unsigned int samples;
bool save(const char* path); bool save(const char* path);
bool initInternal(unsigned char d, int count);
bool init(unsigned int count);
void render();
void* getCurBuf();
unsigned int getCurBufLen();
DivSample(): DivSample():
name(""), name(""),
length(0),
rate(32000), rate(32000),
centerRate(8363), centerRate(8363),
loopStart(-1), loopStart(-1),
loopOffP(0), loopOffP(0),
vol(0),
pitch(0),
depth(16), depth(16),
data(NULL), data8(NULL),
rendLength(0), data16(NULL),
adpcmRendLength(0), data1(NULL),
rendOff(0), dataDPCM(NULL),
rendOffP(0), dataQSoundA(NULL),
rendOffContiguous(0), dataA(NULL),
rendOffQsound(0), dataB(NULL),
rendData(NULL), dataX68(NULL),
adpcmRendData(NULL) {} dataBRR(NULL),
dataVOX(NULL),
length8(0),
length16(0),
length1(0),
lengthDPCM(0),
lengthQSoundA(0),
lengthA(0),
lengthB(0),
lengthX68(0),
lengthBRR(0),
lengthVOX(0),
off8(0),
off16(0),
off1(0),
offDPCM(0),
offQSoundA(0),
offA(0),
offB(0),
offX68(0),
offBRR(0),
offVOX(0),
offSegaPCM(0),
offQSound(0),
samples(0) {}
~DivSample(); ~DivSample();
}; };

View file

@ -41,6 +41,7 @@ enum DivSystem {
DIV_SYSTEM_GB, DIV_SYSTEM_GB,
DIV_SYSTEM_PCE, DIV_SYSTEM_PCE,
DIV_SYSTEM_NES, DIV_SYSTEM_NES,
DIV_SYSTEM_NES_VRC7, // ** COMPOUND SYSTEM - DO NOT USE! **
DIV_SYSTEM_C64_6581, DIV_SYSTEM_C64_6581,
DIV_SYSTEM_C64_8580, DIV_SYSTEM_C64_8580,
DIV_SYSTEM_ARCADE, // ** COMPOUND SYSTEM - DO NOT USE! ** DIV_SYSTEM_ARCADE, // ** COMPOUND SYSTEM - DO NOT USE! **
@ -240,6 +241,7 @@ struct DivSong {
bool pal; bool pal;
bool customTempo; bool customTempo;
int hz, patLen, ordersLen, insLen, waveLen, sampleLen; int hz, patLen, ordersLen, insLen, waveLen, sampleLen;
float masterVol;
float tuning; float tuning;
// compatibility flags // compatibility flags
@ -307,6 +309,7 @@ struct DivSong {
insLen(0), insLen(0),
waveLen(0), waveLen(0),
sampleLen(0), sampleLen(0),
masterVol(1.0f),
tuning(440.0f), tuning(440.0f),
limitSlides(false), limitSlides(false),
linearPitch(true), linearPitch(true),

View file

@ -44,6 +44,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) {
return DIV_SYSTEM_GENESIS_EXT; return DIV_SYSTEM_GENESIS_EXT;
case 0x43: case 0x43:
return DIV_SYSTEM_SMS_OPLL; return DIV_SYSTEM_SMS_OPLL;
case 0x46:
return DIV_SYSTEM_NES_VRC7;
case 0x47: case 0x47:
return DIV_SYSTEM_C64_6581; return DIV_SYSTEM_C64_6581;
case 0x49: case 0x49:
@ -163,6 +165,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) {
return 0x42; return 0x42;
case DIV_SYSTEM_SMS_OPLL: case DIV_SYSTEM_SMS_OPLL:
return 0x43; return 0x43;
case DIV_SYSTEM_NES_VRC7:
return 0x46;
case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_6581:
return 0x47; return 0x47;
case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_EXT:
@ -283,7 +287,9 @@ int DivEngine::getChannelCount(DivSystem sys) {
case DIV_SYSTEM_GENESIS_EXT: case DIV_SYSTEM_GENESIS_EXT:
case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_SMS_OPLL: case DIV_SYSTEM_SMS_OPLL:
return 13; return 13;
case DIV_SYSTEM_NES_VRC7:
return 11;
case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_EXT:
return 16; return 16;
// Furnace-specific systems // Furnace-specific systems
@ -524,6 +530,8 @@ const char* DivEngine::getSystemName(DivSystem sys) {
return "PC Engine/TurboGrafx-16"; return "PC Engine/TurboGrafx-16";
case DIV_SYSTEM_NES: case DIV_SYSTEM_NES:
return "NES"; return "NES";
case DIV_SYSTEM_NES_VRC7:
return "NES + Konami VRC7";
case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_6581:
return "Commodore 64 with 6581"; return "Commodore 64 with 6581";
case DIV_SYSTEM_C64_8580: case DIV_SYSTEM_C64_8580:
@ -645,6 +653,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) {
return "Hudson Soft HuC6280"; return "Hudson Soft HuC6280";
case DIV_SYSTEM_NES: case DIV_SYSTEM_NES:
return "Ricoh 2A03"; return "Ricoh 2A03";
case DIV_SYSTEM_NES_VRC7:
return "Ricoh 2A03 + Konami VRC7";
case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_6581:
return "SID 6581"; return "SID 6581";
case DIV_SYSTEM_C64_8580: case DIV_SYSTEM_C64_8580:
@ -806,6 +816,7 @@ bool DivEngine::isFMSystem(DivSystem sys) {
return (sys==DIV_SYSTEM_GENESIS || return (sys==DIV_SYSTEM_GENESIS ||
sys==DIV_SYSTEM_GENESIS_EXT || sys==DIV_SYSTEM_GENESIS_EXT ||
sys==DIV_SYSTEM_SMS_OPLL || sys==DIV_SYSTEM_SMS_OPLL ||
sys==DIV_SYSTEM_NES_VRC7 ||
sys==DIV_SYSTEM_ARCADE || sys==DIV_SYSTEM_ARCADE ||
sys==DIV_SYSTEM_YM2610 || sys==DIV_SYSTEM_YM2610 ||
sys==DIV_SYSTEM_YM2610_EXT || sys==DIV_SYSTEM_YM2610_EXT ||
@ -1007,6 +1018,7 @@ const char* DivEngine::getChannelName(int chan) {
return chanNames[3][dispatchChanOfChan[chan]]; return chanNames[3][dispatchChanOfChan[chan]];
break; break;
case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL.
case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7.
return "??"; return "??";
break; break;
case DIV_SYSTEM_GB: case DIV_SYSTEM_GB:
@ -1142,6 +1154,7 @@ const char* DivEngine::getChannelShortName(int chan) {
return chanShortNames[3][dispatchChanOfChan[chan]]; return chanShortNames[3][dispatchChanOfChan[chan]];
break; break;
case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL.
case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7.
return "??"; return "??";
break; break;
case DIV_SYSTEM_GB: case DIV_SYSTEM_GB:
@ -1275,6 +1288,7 @@ int DivEngine::getChannelType(int chan) {
return chanTypes[3][dispatchChanOfChan[chan]]; return chanTypes[3][dispatchChanOfChan[chan]];
break; break;
case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL.
case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7.
return 0; return 0;
break; break;
case DIV_SYSTEM_GB: case DIV_SYSTEM_GB:
@ -1408,6 +1422,7 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) {
return chanPrefType[3][dispatchChanOfChan[chan]]; return chanPrefType[3][dispatchChanOfChan[chan]];
break; break;
case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL.
case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7.
return DIV_INS_OPLL; return DIV_INS_OPLL;
break; break;
case DIV_SYSTEM_GB: case DIV_SYSTEM_GB:

View file

@ -310,7 +310,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeS(write.val); // sample number w->writeS(write.val); // sample number
w->writeC((sample->loopStart==0)); // flags w->writeC((sample->loopStart==0)); // flags
if (sample->loopStart>0) { if (sample->loopStart>0) {
loopTimer[streamID]=sample->rendLength; loopTimer[streamID]=sample->length8;
loopSample[streamID]=write.val; loopSample[streamID]=write.val;
} }
} }
@ -848,8 +848,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
for (int i=0; i<song.sampleLen; i++) { for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i]; DivSample* sample=song.sample[i];
logI("setting seek to %d\n",sampleSeek); logI("setting seek to %d\n",sampleSeek);
sample->rendOffContiguous=sampleSeek; sample->off8=sampleSeek;
sampleSeek+=sample->rendLength; sampleSeek+=sample->length8;
} }
if (writeDACSamples) for (int i=0; i<song.sampleLen; i++) { if (writeDACSamples) for (int i=0; i<song.sampleLen; i++) {
@ -857,15 +857,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
w->writeC(0x67); w->writeC(0x67);
w->writeC(0x66); w->writeC(0x66);
w->writeC(0); w->writeC(0);
w->writeI(sample->rendLength); w->writeI(sample->length8);
if (sample->depth==8) { for (unsigned int j=0; j<sample->length8; j++) {
for (unsigned int j=0; j<sample->rendLength; j++) { w->writeC((unsigned char)sample->data8[j]+0x80);
w->writeC((unsigned char)sample->rendData[j]+0x80);
}
} else {
for (unsigned int j=0; j<sample->rendLength; j++) {
w->writeC(((unsigned short)sample->rendData[j]+0x8000)>>8);
}
} }
} }
@ -874,15 +868,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
w->writeC(0x67); w->writeC(0x67);
w->writeC(0x66); w->writeC(0x66);
w->writeC(7); w->writeC(7);
w->writeI(sample->rendLength); w->writeI(sample->length8);
if (sample->depth==8) { for (unsigned int j=0; j<sample->length8; j++) {
for (unsigned int j=0; j<sample->rendLength; j++) { w->writeC(((unsigned char)sample->data8[j]+0x80)>>1);
w->writeC(((unsigned char)sample->rendData[j]+0x80)>>1);
}
} else {
for (unsigned int j=0; j<sample->rendLength; j++) {
w->writeC(((unsigned short)sample->rendData[j]+0x8000)>>9);
}
} }
} }
@ -891,15 +879,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
w->writeC(0x67); w->writeC(0x67);
w->writeC(0x66); w->writeC(0x66);
w->writeC(5); w->writeC(5);
w->writeI(sample->rendLength); w->writeI(sample->length8);
if (sample->depth==8) { for (unsigned int j=0; j<sample->length8; j++) {
for (unsigned int j=0; j<sample->rendLength; j++) { w->writeC(((unsigned char)sample->data8[j]+0x80)>>3);
w->writeC(((unsigned char)sample->rendData[j]+0x80)>>3);
}
} else {
for (unsigned int j=0; j<sample->rendLength; j++) {
w->writeC(((unsigned short)sample->rendData[j]+0x8000)>>11);
}
} }
} }
@ -909,47 +891,29 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
size_t memPos=0; size_t memPos=0;
for (int i=0; i<song.sampleLen; i++) { for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i]; DivSample* sample=song.sample[i];
if ((memPos&0xff0000)!=((memPos+sample->rendLength)&0xff0000)) { if ((memPos&0xff0000)!=((memPos+sample->length8)&0xff0000)) {
memPos=(memPos+0xffff)&0xff0000; memPos=(memPos+0xffff)&0xff0000;
} }
if (memPos>=16777216) break; if (memPos>=16777216) break;
sample->rendOffP=memPos; sample->offSegaPCM=memPos;
unsigned int alignedSize=(sample->rendLength+0xff)&(~0xff); unsigned int alignedSize=(sample->length8+0xff)&(~0xff);
unsigned int readPos=0; unsigned int readPos=0;
if (alignedSize>65536) alignedSize=65536; if (alignedSize>65536) alignedSize=65536;
if (sample->depth==8) { for (unsigned int j=0; j<alignedSize; j++) {
for (unsigned int j=0; j<alignedSize; j++) { if (readPos>=sample->length8) {
if (readPos>=sample->rendLength) { if (sample->loopStart>=0 && sample->loopStart<(int)sample->length8) {
if (sample->loopStart>=0 && sample->loopStart<(int)sample->rendLength) { readPos=sample->loopStart;
readPos=sample->loopStart; pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);
pcmMem[memPos++]=((unsigned char)sample->rendData[readPos]+0x80);
} else {
pcmMem[memPos++]=0x80;
}
} else { } else {
pcmMem[memPos++]=((unsigned char)sample->rendData[readPos]+0x80); pcmMem[memPos++]=0x80;
} }
readPos++; } else {
if (memPos>=16777216) break; pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);
} }
sample->loopOffP=readPos-sample->loopStart; readPos++;
} else { if (memPos>=16777216) break;
for (unsigned int j=0; j<alignedSize; j++) {
if (readPos>=sample->rendLength) {
if (sample->loopStart>=0 && sample->loopStart<(int)sample->rendLength) {
readPos=sample->loopStart;
pcmMem[memPos++]=(((unsigned short)sample->rendData[readPos]+0x8000)>>8);
} else {
pcmMem[memPos++]=0x80;
}
} else {
pcmMem[memPos++]=(((unsigned short)sample->rendData[readPos]+0x8000)>>8);
}
readPos++;
if (memPos>=16777216) break;
}
sample->loopOffP=readPos-sample->loopStart;
} }
sample->loopOffP=readPos-sample->loopStart;
if (memPos>=16777216) break; if (memPos>=16777216) break;
} }
@ -964,14 +928,14 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
delete[] pcmMem; delete[] pcmMem;
} }
if (writeADPCM && adpcmMemLen>0) { if (writeADPCM && adpcmAMemLen>0) {
w->writeC(0x67); w->writeC(0x67);
w->writeC(0x66); w->writeC(0x66);
w->writeC(0x82); w->writeC(0x82);
w->writeI(adpcmMemLen+8); w->writeI(adpcmAMemLen+8);
w->writeI(adpcmMemLen); w->writeI(adpcmAMemLen);
w->writeI(0); w->writeI(0);
w->write(adpcmMem,adpcmMemLen); w->write(adpcmAMem,adpcmAMemLen);
} }
if (writeQSound && qsoundMemLen>0) { if (writeQSound && qsoundMemLen>0) {
@ -1137,12 +1101,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
if (loopSample[nextToTouch]<song.sampleLen) { if (loopSample[nextToTouch]<song.sampleLen) {
DivSample* sample=song.sample[loopSample[nextToTouch]]; DivSample* sample=song.sample[loopSample[nextToTouch]];
// insert loop // insert loop
if (sample->loopStart<(int)sample->rendLength) { if (sample->loopStart<(int)sample->length8) {
w->writeC(0x93); w->writeC(0x93);
w->writeC(nextToTouch); w->writeC(nextToTouch);
w->writeI(sample->rendOffContiguous+sample->loopStart); w->writeI(sample->off8+sample->loopStart);
w->writeC(0x81); w->writeC(0x81);
w->writeI(sample->rendLength-sample->loopStart); w->writeI(sample->length8-sample->loopStart);
} }
} }
loopSample[nextToTouch]=-1; loopSample[nextToTouch]=-1;

View file

@ -1266,7 +1266,8 @@ void FurnaceGUI::drawSampleEdit() {
} else { } else {
DivSample* sample=e->song.sample[curSample]; DivSample* sample=e->song.sample[curSample];
ImGui::InputText("Name",&sample->name); ImGui::InputText("Name",&sample->name);
ImGui::Text("Length: %d",sample->length); ImGui::Text("Length: %d",sample->samples);
ImGui::Text("Type: %d-bit",sample->depth);
if (ImGui::InputInt("Rate (Hz)",&sample->rate,10,200)) { if (ImGui::InputInt("Rate (Hz)",&sample->rate,10,200)) {
if (sample->rate<100) sample->rate=100; if (sample->rate<100) sample->rate=100;
if (sample->rate>96000) sample->rate=96000; if (sample->rate>96000) sample->rate=96000;
@ -1287,19 +1288,11 @@ void FurnaceGUI::drawSampleEdit() {
if (doLoop) { if (doLoop) {
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) {
if (sample->loopStart<0 || sample->loopStart>=sample->length) { if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) {
sample->loopStart=0; sample->loopStart=0;
} }
} }
} }
if (ImGui::SliderScalar("Volume",ImGuiDataType_S8,&sample->vol,&_ZERO,&_ONE_HUNDRED,fmt::sprintf("%d%%%%",sample->vol*2).c_str())) {
if (sample->vol<0) sample->vol=0;
if (sample->vol>100) sample->vol=100;
}
if (ImGui::SliderScalar("Pitch",ImGuiDataType_S8,&sample->pitch,&_ZERO,&_TEN,pitchLabel[sample->pitch])) {
if (sample->pitch<0) sample->pitch=0;
if (sample->pitch>10) sample->pitch=10;
}
if (ImGui::Button("Apply")) { if (ImGui::Button("Apply")) {
e->renderSamplesP(); e->renderSamplesP();
} }
@ -1321,15 +1314,15 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga"); ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga");
} }
} }
if (sample->length&1) { if (sample->samples&1) {
considerations=true; considerations=true;
ImGui::Text("- sample length will be aligned to the nearest even sample on Amiga"); ImGui::Text("- sample length will be aligned to the nearest even sample on Amiga");
} }
if (sample->length>65535) { if (sample->samples>65535) {
considerations=true; considerations=true;
ImGui::Text("- maximum sample length on Sega PCM is 65536 samples"); ImGui::Text("- maximum sample length on Sega PCM is 65536 samples");
} }
if (sample->length>2097151) { if (sample->samples>2097151) {
considerations=true; considerations=true;
ImGui::Text("- maximum sample length on Neo Geo ADPCM is 2097152 samples"); ImGui::Text("- maximum sample length on Neo Geo ADPCM is 2097152 samples");
} }
@ -1352,6 +1345,10 @@ void FurnaceGUI::drawMixer() {
ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
if (ImGui::Begin("Mixer",&mixerOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { if (ImGui::Begin("Mixer",&mixerOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) {
char id[32]; char id[32];
if (ImGui::SliderFloat("Master Volume",&e->song.masterVol,0,3,"%.2fx")) {
if (e->song.masterVol<0) e->song.masterVol=0;
if (e->song.masterVol>3) e->song.masterVol=3;
}
for (int i=0; i<e->song.systemLen; i++) { for (int i=0; i<e->song.systemLen; i++) {
snprintf(id,31,"MixS%d",i); snprintf(id,31,"MixS%d",i);
bool doInvert=e->song.systemVol[i]&128; bool doInvert=e->song.systemVol[i]&128;
@ -1527,6 +1524,7 @@ const char* aboutLine[]={
"0x5066", "0x5066",
"breakthetargets", "breakthetargets",
"kleeder", "kleeder",
"nicco1690",
"NikonTeen", "NikonTeen",
"SuperJet Spade", "SuperJet Spade",
"TheDuccinator", "TheDuccinator",
@ -1908,12 +1906,12 @@ void FurnaceGUI::drawStats() {
} }
if (!statsOpen) return; if (!statsOpen) return;
if (ImGui::Begin("Statistics",&statsOpen)) { if (ImGui::Begin("Statistics",&statsOpen)) {
String adpcmUsage=fmt::sprintf("%d/16384KB",e->adpcmMemLen/1024); String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024);
String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024);
String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024);
ImGui::Text("ADPCM-A"); ImGui::Text("ADPCM-A");
ImGui::SameLine(); ImGui::SameLine();
ImGui::ProgressBar(((float)e->adpcmMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmUsage.c_str()); ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str());
ImGui::Text("ADPCM-B"); ImGui::Text("ADPCM-B");
ImGui::SameLine(); ImGui::SameLine();
ImGui::ProgressBar(((float)e->adpcmBMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmBUsage.c_str()); ImGui::ProgressBar(((float)e->adpcmBMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmBUsage.c_str());
@ -4550,6 +4548,10 @@ bool FurnaceGUI::loop() {
e->setSysFlags(i,(flags&(~3))|2,restart); e->setSysFlags(i,(flags&(~3))|2,restart);
updateWindowTitle(); updateWindowTitle();
} }
if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&3)==3)) {
e->setSysFlags(i,(flags&(~3))|3,restart);
updateWindowTitle();
}
ImGui::Text("Chip type:"); ImGui::Text("Chip type:");
if (ImGui::RadioButton("Sega VDP/Master System",((flags>>2)&3)==0)) { if (ImGui::RadioButton("Sega VDP/Master System",((flags>>2)&3)==0)) {
e->setSysFlags(i,(flags&(~12))|0,restart); e->setSysFlags(i,(flags&(~12))|0,restart);