Merge branch 'master' of https://github.com/tildearrow/furnace into ym2610b

This commit is contained in:
cam900 2022-02-28 03:04:28 +09:00
commit 6d36a8fdd2
5 changed files with 137 additions and 96 deletions

View file

@ -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. 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 # information
files may be zlib-compressed, but Furnace accepts uncompressed files as well.
all numbers are little-endian. all numbers are little-endian.
the following fields may be found in "size": the following fields may be found in "size":
@ -71,6 +75,7 @@ the format versions are:
the header is 32 bytes long. the header is 32 bytes long.
```
size | description size | description
-----|------------------------------------ -----|------------------------------------
16 | "-Furnace module-" format magic 16 | "-Furnace module-" format magic
@ -78,9 +83,11 @@ size | description
2 | reserved 2 | reserved
4 | song info pointer 4 | song info pointer
8 | reserved 8 | reserved
```
# song info # song info
```
size | description size | description
-----|------------------------------------ -----|------------------------------------
4 | "INFO" block ID 4 | "INFO" block ID
@ -208,9 +215,11 @@ size | description
STR | song comment STR | song comment
4f | master volume, 1.0f=100% (>=59) 4f | master volume, 1.0f=100% (>=59)
| this is 2.0f for modules before 59 | this is 2.0f for modules before 59
```
# instrument # instrument
```
size | description size | description
-----|------------------------------------ -----|------------------------------------
4 | "INST" block ID 4 | "INST" block ID
@ -418,9 +427,11 @@ size | description
4 | DT macro release 4 | DT macro release
4 | D2R macro release 4 | D2R macro release
4 | SSG-EG macro release 4 | SSG-EG macro release
```
# wavetable # wavetable
```
size | description size | description
-----|------------------------------------ -----|------------------------------------
4 | "WAVE" block ID 4 | "WAVE" block ID
@ -430,9 +441,11 @@ size | description
4 | wavetable min 4 | wavetable min
4 | wavetable max 4 | wavetable max
4?? | wavetable data 4?? | wavetable data
```
# sample # sample
```
size | description size | description
-----|------------------------------------ -----|------------------------------------
4 | "SMPL" block ID 4 | "SMPL" block ID
@ -460,9 +473,11 @@ size | description
??? | sample data ??? | sample data
| - version<58 size is length*2 | - version<58 size is length*2
| - version>=58 size is length | - version>=58 size is length
```
# pattern # pattern
```
size | description size | description
-----|------------------------------------ -----|------------------------------------
4 | "PATR" block ID 4 | "PATR" block ID
@ -479,11 +494,13 @@ size | description
| - volume | - volume
| - effect and effect data... | - effect and effect data...
STR | pattern name (>=51) STR | pattern name (>=51)
```
# the Furnace instrument format (.fui) # 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. the instrument format is pretty similar to the file format, but it also stores wavetables and samples used by the instrument.
```
size | description size | description
-----|------------------------------------ -----|------------------------------------
16 | "-Furnace instr.-" format magic 16 | "-Furnace instr.-" format magic
@ -495,6 +512,7 @@ size | description
4 | reserved 4 | reserved
4?? | pointers to wavetables 4?? | pointers to wavetables
4?? | pointers to samples 4?? | pointers to samples
```
instrument data follows. instrument data follows.
@ -502,10 +520,12 @@ instrument data follows.
similar to the instrument format... similar to the instrument format...
```
size | description size | description
-----|------------------------------------ -----|------------------------------------
16 | "-Furnace waveta-" format magic 16 | "-Furnace waveta-" format magic
2 | format version 2 | format version
2 | reserved 2 | reserved
```
wavetable data follows. wavetable data follows.

View file

@ -80,29 +80,31 @@ void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size
static int os; static int os;
for (size_t h=start; h<start+len; h++) { for (size_t h=start; h<start+len; h++) {
if (!writes.empty() && --delay<0) { os=0;
// 84 is safe value for (int i=0; i<9; i++) {
QueuedWrite& w=writes.front(); if (!writes.empty() && --delay<0) {
if (w.addrOrVal) { // 84 is safe value
OPLL_Write(&fm,1,w.val); QueuedWrite& w=writes.front();
//printf("write: %x = %.2x\n",w.addr,w.val); if (w.addrOrVal) {
regPool[w.addr&0xff]=w.val; OPLL_Write(&fm,1,w.val);
writes.pop(); //printf("write: %x = %.2x\n",w.addr,w.val);
delay=21; regPool[w.addr&0xff]=w.val;
} else { writes.pop();
//printf("busycounter: %d\n",lastBusy); delay=21;
OPLL_Write(&fm,0,w.addr); } else {
w.addrOrVal=true; //printf("busycounter: %d\n",lastBusy);
delay=3; OPLL_Write(&fm,0,w.addr);
w.addrOrVal=true;
delay=3;
}
} }
}
OPLL_Clock(&fm,o); OPLL_Clock(&fm,o);
os=(o[0]+o[1])<<7; os+=(o[0]+o[1]);
}
os*=50;
if (os<-32768) os=-32768; if (os<-32768) os=-32768;
if (os>32767) os=32767; if (os>32767) os=32767;
//printf("%d\n",o[0]);
bufL[h]=os; bufL[h]=os;
} }
} }
@ -239,7 +241,12 @@ void DivPlatformOPLL::tick() {
}*/ }*/
if (chan[i].keyOn || chan[i].keyOff) { 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].keyOn=false;
chan[i].keyOff=false; chan[i].keyOff=false;
} }
@ -259,9 +266,16 @@ void DivPlatformOPLL::tick() {
int freqt=toFreq(chan[i].freq); int freqt=toFreq(chan[i].freq);
chan[i].freqH=freqt>>8; chan[i].freqH=freqt>>8;
chan[i].freqL=freqt&0xff; 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(0x28,0xf0|konOffs[i]);
immWrite(0x20+i,(chan[i].freqH)|(chan[i].active<<4)|(chan[i].state.alg?0x20:0)); immWrite(0x20+i,(chan[i].freqH)|(chan[i].active<<4)|(chan[i].state.alg?0x20:0));
chan[i].keyOn=false; chan[i].keyOn=false;
@ -359,7 +373,22 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
rWrite(0x06,(mod.sl<<4)|(mod.rr)); rWrite(0x06,(mod.sl<<4)|(mod.rr));
rWrite(0x07,(car.sl<<4)|(car.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; chan[c.chan].insChanged=false;
@ -367,6 +396,29 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].note=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].freqChanged=true;
} }
chan[c.chan].keyOn=true; chan[c.chan].keyOn=true;
@ -392,7 +444,9 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
if (!chan[c.chan].std.hasVol) { if (!chan[c.chan].std.hasVol) {
chan[c.chan].outVol=c.value; 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; break;
} }
case DIV_CMD_GET_VOLUME: { case DIV_CMD_GET_VOLUME: {
@ -570,6 +624,13 @@ void DivPlatformOPLL::reset() {
} }
lastBusy=60; lastBusy=60;
drumState=0;
drumVol[0]=0;
drumVol[1]=0;
drumVol[2]=0;
drumVol[3]=0;
drumVol[4]=0;
delay=0; delay=0;
} }
@ -619,7 +680,7 @@ void DivPlatformOPLL::setFlags(unsigned int flags) {
} else { } else {
chipClock=COLOR_NTSC/4.0; chipClock=COLOR_NTSC/4.0;
} }
rate=chipClock; rate=chipClock/9;
} }
int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {

View file

@ -35,7 +35,7 @@ class DivPlatformOPLL: public DivDispatch {
unsigned char freqH, freqL; unsigned char freqH, freqL;
int freq, baseFreq, pitch, note; int freq, baseFreq, pitch, note;
unsigned char ins; 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; int vol, outVol;
unsigned char pan; unsigned char pan;
Channel(): Channel():
@ -51,6 +51,7 @@ class DivPlatformOPLL: public DivDispatch {
freqChanged(false), freqChanged(false),
keyOn(false), keyOn(false),
keyOff(false), keyOff(false),
drums(false),
portaPause(false), portaPause(false),
furnaceDac(false), furnaceDac(false),
inPorta(false), inPorta(false),
@ -69,6 +70,8 @@ class DivPlatformOPLL: public DivDispatch {
opll_t fm; opll_t fm;
int delay; int delay;
unsigned char lastBusy; unsigned char lastBusy;
unsigned char drumState;
unsigned char drumVol[5];
unsigned char regPool[256]; unsigned char regPool[256];

View file

@ -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 // Echo is output on the unfiltered component of the left channel and
// the filtered component of the right channel. // the filtered component of the right channel.
int32_t wet = (ch == 1) ? echo_output<<16 : 0; int32_t wet = (ch == 1) ? echo_output<<14 : 0;
int32_t dry = (ch == 0) ? echo_output<<16 : 0; int32_t dry = (ch == 0) ? echo_output<<14 : 0;
int32_t output = 0; int32_t output = 0;
for(int v=0; v<19; v++) for(int v=0; v<19; v++)

View file

@ -20,15 +20,15 @@
#include "engine.h" #include "engine.h"
#include "../ta-log.h" #include "../ta-log.h"
#include "../utfutils.h" #include "../utfutils.h"
#include "song.h"
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; 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) { 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 if (write.addr==0xffffffff) { // Furnace fake reset
switch (sys) { switch (sys) {
case DIV_SYSTEM_GENESIS:
case DIV_SYSTEM_GENESIS_EXT:
case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
for (int i=0; i<3; i++) { // set SL and RR to highest for (int i=0; i<3; i++) { // set SL and RR to highest
w->writeC(isSecond?0xa2:0x52); w->writeC(isSecond?0xa2:0x52);
w->writeC(0x80+i); 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(isSecond?0xa2:0x52); // disable DAC
w->writeC(0x2b); w->writeC(0x2b);
w->writeC(0); 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; break;
case DIV_SYSTEM_SMS: case DIV_SYSTEM_SMS:
for (int i=0; i<4; i++) { 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(isSecond?0x95:0x15);
w->writeC(0); w->writeC(0);
break; break;
case DIV_SYSTEM_ARCADE:
case DIV_SYSTEM_YM2151: case DIV_SYSTEM_YM2151:
for (int i=0; i<8; i++) { for (int i=0; i<8; i++) {
w->writeC(isSecond?0xa4:0x54); w->writeC(isSecond?0xa4:0x54);
@ -148,12 +141,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0x08); w->writeC(0x08);
w->writeC(i); w->writeC(i);
} }
if (sys==DIV_SYSTEM_ARCADE) { break;
for (int i=0; i<5; i++) { case DIV_SYSTEM_SEGAPCM:
w->writeC(0xc0); case DIV_SYSTEM_SEGAPCM_COMPAT:
w->writeS((isSecond?0x8086:0x86)+(i<<3)); for (int i=0; i<16; i++) {
w->writeC(3); w->writeC(0xc0);
} w->writeS((isSecond?0x8086:0x86)+(i<<3));
w->writeC(3);
} }
break; break;
case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610:
@ -303,6 +297,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
} }
if (write.addr>=0xffff0000) { // Furnace special command if (write.addr>=0xffff0000) { // Furnace special command
unsigned char streamID=streamOff+((write.addr&0xff00)>>8); 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) { switch (write.addr&0xff) {
case 0: // play sample case 0: // play sample
if (write.val<song.sampleLen) { if (write.val<song.sampleLen) {
@ -332,9 +327,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
return; return;
} }
switch (sys) { switch (sys) {
case DIV_SYSTEM_GENESIS:
case DIV_SYSTEM_GENESIS_EXT:
case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
switch (write.addr>>8) { switch (write.addr>>8) {
case 0: // port 0 case 0: // port 0
w->writeC(isSecond?0xa2:0x52); 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((isSecond?0x80:0)|(write.addr&0xff));
w->writeC(write.val); w->writeC(write.val);
break; break;
case DIV_SYSTEM_ARCADE:
case DIV_SYSTEM_YM2151: case DIV_SYSTEM_YM2151:
switch (write.addr>>16) { w->writeC(isSecond?0xa4:0x54);
case 0: // YM2151 w->writeC(write.addr&0xff);
w->writeC(isSecond?0xa4:0x54); w->writeC(write.val);
w->writeC(write.addr&0xff); break;
w->writeC(write.val); case DIV_SYSTEM_SEGAPCM:
break; case DIV_SYSTEM_SEGAPCM_COMPAT:
case 1: // SegaPCM w->writeC(0xc0);
w->writeC(0xc0); w->writeS((isSecond?0x8000:0)|(write.addr&0xffff));
w->writeS((isSecond?0x8000:0)|(write.addr&0xffff)); w->writeC(write.val);
w->writeC(write.val);
break;
}
break; break;
case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL:
@ -550,30 +540,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
if (!sysToExport[i]) continue; if (!sysToExport[i]) continue;
} }
switch (song.system[i]) { 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: case DIV_SYSTEM_SMS:
if (!hasSN) { if (!hasSN) {
hasSN=disCont[i].dispatch->chipClock; hasSN=disCont[i].dispatch->chipClock;
@ -634,17 +600,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
howManyChips++; howManyChips++;
} }
break; break;
case DIV_SYSTEM_ARCADE: case DIV_SYSTEM_SEGAPCM:
if (!hasOPM) { case DIV_SYSTEM_SEGAPCM_COMPAT:
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!");
}
if (!hasSegaPCM) { if (!hasSegaPCM) {
hasSegaPCM=4000000; hasSegaPCM=4000000;
willExport[i]=true; willExport[i]=true;
@ -654,7 +611,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
willExport[i]=true; willExport[i]=true;
hasSegaPCM|=0x40000000; hasSegaPCM|=0x40000000;
howManyChips++; howManyChips++;
addWarning("adding a compound system two times is experimental!");
} }
break; break;
case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610:
@ -703,6 +659,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
} }
break; break;
case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
if (!hasOPN2) { if (!hasOPN2) {
hasOPN2=disCont[i].dispatch->chipClock; hasOPN2=disCont[i].dispatch->chipClock;
willExport[i]=true; willExport[i]=true;
@ -978,8 +935,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) {
if (!willExport[i]) continue; if (!willExport[i]) continue;
streamIDs[i]=streamID; streamIDs[i]=streamID;
switch (song.system[i]) { switch (song.system[i]) {
case DIV_SYSTEM_GENESIS: case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_GENESIS_EXT: case DIV_SYSTEM_YM2612_EXT:
w->writeC(0x90); w->writeC(0x90);
w->writeC(streamID); w->writeC(streamID);
w->writeC(0x02); w->writeC(0x02);