diff --git a/papers/format.md b/papers/format.md index edac91c13..21beaf0aa 100644 --- a/papers/format.md +++ b/papers/format.md @@ -4,8 +4,12 @@ while Furnace works directly with the .dmf format, I had to create a new format this document has the goal of detailing the format. +**notice:** GitHub's Markdown formatter may break on this file as it doesn't seem to treat tables correctly. + # information +files may be zlib-compressed, but Furnace accepts uncompressed files as well. + all numbers are little-endian. the following fields may be found in "size": @@ -71,6 +75,7 @@ the format versions are: the header is 32 bytes long. +``` size | description -----|------------------------------------ 16 | "-Furnace module-" format magic @@ -78,9 +83,11 @@ size | description 2 | reserved 4 | song info pointer 8 | reserved +``` # song info +``` size | description -----|------------------------------------ 4 | "INFO" block ID @@ -208,9 +215,11 @@ size | description STR | song comment 4f | master volume, 1.0f=100% (>=59) | this is 2.0f for modules before 59 +``` # instrument +``` size | description -----|------------------------------------ 4 | "INST" block ID @@ -418,9 +427,11 @@ size | description 4 | DT macro release 4 | D2R macro release 4 | SSG-EG macro release +``` # wavetable +``` size | description -----|------------------------------------ 4 | "WAVE" block ID @@ -430,9 +441,11 @@ size | description 4 | wavetable min 4 | wavetable max 4?? | wavetable data +``` # sample +``` size | description -----|------------------------------------ 4 | "SMPL" block ID @@ -460,9 +473,11 @@ size | description ??? | sample data | - version<58 size is length*2 | - version>=58 size is length +``` # pattern +``` size | description -----|------------------------------------ 4 | "PATR" block ID @@ -479,11 +494,13 @@ size | description | - volume | - effect and effect data... STR | pattern name (>=51) +``` # the Furnace instrument format (.fui) the instrument format is pretty similar to the file format, but it also stores wavetables and samples used by the instrument. +``` size | description -----|------------------------------------ 16 | "-Furnace instr.-" format magic @@ -495,6 +512,7 @@ size | description 4 | reserved 4?? | pointers to wavetables 4?? | pointers to samples +``` instrument data follows. @@ -502,10 +520,12 @@ instrument data follows. similar to the instrument format... +``` size | description -----|------------------------------------ 16 | "-Furnace waveta-" format magic 2 | format version 2 | reserved +``` wavetable data follows. diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index ad3b3a22e..28285931a 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -80,29 +80,31 @@ void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size static int os; for (size_t h=start; h32767) os=32767; - //printf("%d\n",o[0]); - bufL[h]=os; } } @@ -239,7 +241,12 @@ void DivPlatformOPLL::tick() { }*/ if (chan[i].keyOn || chan[i].keyOff) { - immWrite(0x20+i,(chan[i].freqH)|(chan[i].state.alg?0x20:0)); + if (chan[i].drums) { + drumState&=~(0x10>>(chan[i].note%12)); + immWrite(0x0e,0x20|drumState); + } else { + immWrite(0x20+i,(chan[i].freqH)/*|(chan[i].state.alg?0x20:0)*/); + } //chan[i].keyOn=false; chan[i].keyOff=false; } @@ -259,9 +266,16 @@ void DivPlatformOPLL::tick() { int freqt=toFreq(chan[i].freq); chan[i].freqH=freqt>>8; chan[i].freqL=freqt&0xff; - immWrite(0x10+i,freqt&0xff); + if (!chan[i].drums) { + immWrite(0x10+i,freqt&0xff); + } } - if (chan[i].keyOn || chan[i].freqChanged) { + if (chan[i].keyOn && chan[i].drums) { + //printf("%d\n",chan[i].note%12); + drumState|=(0x10>>(chan[i].note%12)); + immWrite(0x0e,0x20|drumState); + chan[i].keyOn=false; + } else if (chan[i].keyOn || chan[i].freqChanged) { //immWrite(0x28,0xf0|konOffs[i]); immWrite(0x20+i,(chan[i].freqH)|(chan[i].active<<4)|(chan[i].state.alg?0x20:0)); chan[i].keyOn=false; @@ -359,7 +373,22 @@ int DivPlatformOPLL::dispatch(DivCommand c) { rWrite(0x06,(mod.sl<<4)|(mod.rr)); rWrite(0x07,(car.sl<<4)|(car.rr)); } - rWrite(0x30+c.chan,(15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)|(chan[c.chan].state.opllPreset<<4)); + if (chan[c.chan].state.opllPreset==16) { // compatible drums mode + chan[c.chan].drums=true; + immWrite(0x16,0x20); + immWrite(0x26,0x05); + immWrite(0x16,0x20); + immWrite(0x26,0x05); + immWrite(0x17,0x50); + immWrite(0x27,0x05); + immWrite(0x17,0x50); + immWrite(0x27,0x05); + immWrite(0x18,0xC0); + immWrite(0x28,0x01); + } else { + chan[c.chan].drums=false; + rWrite(0x30+c.chan,(15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)|(chan[c.chan].state.opllPreset<<4)); + } } chan[c.chan].insChanged=false; @@ -367,6 +396,29 @@ int DivPlatformOPLL::dispatch(DivCommand c) { if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].note=c.value; + + if (chan[c.chan].drums) { + switch (chan[c.chan].note%12) { + case 0: // kick + drumVol[0]=(15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15); + break; + case 1: // snare + drumVol[1]=(15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15); + break; + case 2: // tom + drumVol[2]=(15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15); + break; + case 3: // top + drumVol[3]=(15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15); + break; + default: // hi-hat + drumVol[4]=(15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15); + break; + } + rWrite(0x36,drumVol[0]); + rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); + rWrite(0x38,drumVol[3]|(drumVol[2]<<4)); + } chan[c.chan].freqChanged=true; } chan[c.chan].keyOn=true; @@ -392,7 +444,9 @@ int DivPlatformOPLL::dispatch(DivCommand c) { if (!chan[c.chan].std.hasVol) { chan[c.chan].outVol=c.value; } - rWrite(0x30+c.chan,(15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)|(chan[c.chan].state.opllPreset<<4)); + if (!chan[c.chan].drums) { + rWrite(0x30+c.chan,(15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)|(chan[c.chan].state.opllPreset<<4)); + } break; } case DIV_CMD_GET_VOLUME: { @@ -570,6 +624,13 @@ void DivPlatformOPLL::reset() { } lastBusy=60; + drumState=0; + + drumVol[0]=0; + drumVol[1]=0; + drumVol[2]=0; + drumVol[3]=0; + drumVol[4]=0; delay=0; } @@ -619,7 +680,7 @@ void DivPlatformOPLL::setFlags(unsigned int flags) { } else { chipClock=COLOR_NTSC/4.0; } - rate=chipClock; + rate=chipClock/9; } int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/opll.h b/src/engine/platform/opll.h index 2c950cabc..5db8e26a8 100644 --- a/src/engine/platform/opll.h +++ b/src/engine/platform/opll.h @@ -35,7 +35,7 @@ class DivPlatformOPLL: public DivDispatch { unsigned char freqH, freqL; int freq, baseFreq, pitch, note; unsigned char ins; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, drums, portaPause, furnaceDac, inPorta; int vol, outVol; unsigned char pan; Channel(): @@ -51,6 +51,7 @@ class DivPlatformOPLL: public DivDispatch { freqChanged(false), keyOn(false), keyOff(false), + drums(false), portaPause(false), furnaceDac(false), inPorta(false), @@ -69,6 +70,8 @@ class DivPlatformOPLL: public DivDispatch { opll_t fm; int delay; unsigned char lastBusy; + unsigned char drumState; + unsigned char drumVol[5]; unsigned char regPool[256]; diff --git a/src/engine/platform/sound/qsound.c b/src/engine/platform/sound/qsound.c index b52475b36..8e4d6fa97 100644 --- a/src/engine/platform/sound/qsound.c +++ b/src/engine/platform/sound/qsound.c @@ -601,8 +601,8 @@ static void state_normal_update(struct qsound_chip *chip) { // Echo is output on the unfiltered component of the left channel and // the filtered component of the right channel. - int32_t wet = (ch == 1) ? echo_output<<16 : 0; - int32_t dry = (ch == 0) ? echo_output<<16 : 0; + int32_t wet = (ch == 1) ? echo_output<<14 : 0; + int32_t dry = (ch == 0) ? echo_output<<14 : 0; int32_t output = 0; for(int v=0; v<19; v++) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index fd5fa3ce4..71f313e43 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -20,15 +20,15 @@ #include "engine.h" #include "../ta-log.h" #include "../utfutils.h" +#include "song.h" constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool isSecond) { if (write.addr==0xffffffff) { // Furnace fake reset switch (sys) { - case DIV_SYSTEM_GENESIS: - case DIV_SYSTEM_GENESIS_EXT: case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: for (int i=0; i<3; i++) { // set SL and RR to highest w->writeC(isSecond?0xa2:0x52); w->writeC(0x80+i); @@ -67,12 +67,6 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(isSecond?0xa2:0x52); // disable DAC w->writeC(0x2b); w->writeC(0); - if (sys!=DIV_SYSTEM_YM2612) { - for (int i=0; i<4; i++) { - w->writeC(isSecond?0x30:0x50); - w->writeC(0x90|(i<<5)|15); - } - } break; case DIV_SYSTEM_SMS: for (int i=0; i<4; i++) { @@ -128,7 +122,6 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(isSecond?0x95:0x15); w->writeC(0); break; - case DIV_SYSTEM_ARCADE: case DIV_SYSTEM_YM2151: for (int i=0; i<8; i++) { w->writeC(isSecond?0xa4:0x54); @@ -148,12 +141,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(0x08); w->writeC(i); } - if (sys==DIV_SYSTEM_ARCADE) { - for (int i=0; i<5; i++) { - w->writeC(0xc0); - w->writeS((isSecond?0x8086:0x86)+(i<<3)); - w->writeC(3); - } + break; + case DIV_SYSTEM_SEGAPCM: + case DIV_SYSTEM_SEGAPCM_COMPAT: + for (int i=0; i<16; i++) { + w->writeC(0xc0); + w->writeS((isSecond?0x8086:0x86)+(i<<3)); + w->writeC(3); } break; case DIV_SYSTEM_YM2610: @@ -303,6 +297,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write } if (write.addr>=0xffff0000) { // Furnace special command unsigned char streamID=streamOff+((write.addr&0xff00)>>8); + logD("writing stream command %x:%x with stream ID %d\n",write.addr,write.val,streamID); switch (write.addr&0xff) { case 0: // play sample if (write.val>8) { case 0: // port 0 w->writeC(isSecond?0xa2:0x52); @@ -371,20 +365,16 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC((isSecond?0x80:0)|(write.addr&0xff)); w->writeC(write.val); break; - case DIV_SYSTEM_ARCADE: case DIV_SYSTEM_YM2151: - switch (write.addr>>16) { - case 0: // YM2151 - w->writeC(isSecond?0xa4:0x54); - w->writeC(write.addr&0xff); - w->writeC(write.val); - break; - case 1: // SegaPCM - w->writeC(0xc0); - w->writeS((isSecond?0x8000:0)|(write.addr&0xffff)); - w->writeC(write.val); - break; - } + w->writeC(isSecond?0xa4:0x54); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; + case DIV_SYSTEM_SEGAPCM: + case DIV_SYSTEM_SEGAPCM_COMPAT: + w->writeC(0xc0); + w->writeS((isSecond?0x8000:0)|(write.addr&0xffff)); + w->writeC(write.val); break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: @@ -550,30 +540,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { if (!sysToExport[i]) continue; } switch (song.system[i]) { - case DIV_SYSTEM_GENESIS: - case DIV_SYSTEM_GENESIS_EXT: - writeDACSamples=true; - if (!hasOPN2) { - hasOPN2=disCont[i].dispatch->chipClock; - willExport[i]=true; - } else if (!(hasOPN2&0x40000000)) { - isSecond[i]=true; - willExport[i]=true; - hasOPN2|=0x40000000; - howManyChips++; - addWarning("adding a compound system two times is experimental!"); - } - if (!hasSN) { - hasSN=3579545; - willExport[i]=true; - } else if (!(hasSN&0x40000000)) { - isSecond[i]=true; - willExport[i]=true; - hasSN|=0x40000000; - howManyChips++; - addWarning("adding a compound system two times is experimental!"); - } - break; case DIV_SYSTEM_SMS: if (!hasSN) { hasSN=disCont[i].dispatch->chipClock; @@ -634,17 +600,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { howManyChips++; } break; - case DIV_SYSTEM_ARCADE: - if (!hasOPM) { - hasOPM=disCont[i].dispatch->chipClock; - willExport[i]=true; - } else if (!(hasOPM&0x40000000)) { - isSecond[i]=true; - willExport[i]=true; - hasOPM|=0x40000000; - howManyChips++; - addWarning("adding a compound system two times is experimental!"); - } + case DIV_SYSTEM_SEGAPCM: + case DIV_SYSTEM_SEGAPCM_COMPAT: if (!hasSegaPCM) { hasSegaPCM=4000000; willExport[i]=true; @@ -654,7 +611,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { willExport[i]=true; hasSegaPCM|=0x40000000; howManyChips++; - addWarning("adding a compound system two times is experimental!"); } break; case DIV_SYSTEM_YM2610: @@ -703,6 +659,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { } break; case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: if (!hasOPN2) { hasOPN2=disCont[i].dispatch->chipClock; willExport[i]=true; @@ -978,8 +935,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { if (!willExport[i]) continue; streamIDs[i]=streamID; switch (song.system[i]) { - case DIV_SYSTEM_GENESIS: - case DIV_SYSTEM_GENESIS_EXT: + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: w->writeC(0x90); w->writeC(streamID); w->writeC(0x02);