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

This commit is contained in:
cam900 2022-08-11 22:22:05 +09:00
commit 7d83cbb7d6
18 changed files with 290 additions and 58 deletions

View file

@ -1,6 +1,5 @@
# to-do for 0.6pre1.5-0.6pre2 # to-do for 0.6pre1.5-0.6pre2
- Game Boy envelope macro/sequence
- volume commands should work on Game Boy - volume commands should work on Game Boy
- ability to customize `OFF`, `===` and `REL` - ability to customize `OFF`, `===` and `REL`
- stereo separation control for AY - stereo separation control for AY

View file

@ -1,5 +1,7 @@
# Yamaha OPZ (YM2414) # Yamaha OPZ (YM2414)
**disclaimer: despite the name, this has nothing to do with teenage engineering's OP-Z synth!**
this is the YM2151's little-known successor, used in the Yamaha TX81Z and a few other Yamaha synthesizers. oh, and the Korg Z3 too. this is the YM2151's little-known successor, used in the Yamaha TX81Z and a few other Yamaha synthesizers. oh, and the Korg Z3 too.
it adds these features on top of the YM2151: it adds these features on top of the YM2151:

View file

@ -32,7 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
the format versions are: the format versions are:
- 105: Furance dev105 - 106: Furnace dev106
- 105: Furnace dev105
- 104: Furnace dev104 - 104: Furnace dev104
- 103: Furnace dev103 - 103: Furnace dev103
- 102: Furnace 0.6pre1 (dev102) - 102: Furnace 0.6pre1 (dev102)
@ -846,6 +847,9 @@ size | description
| - 2 bytes: nothing | - 2 bytes: nothing
| - for loop/loop until release: | - for loop/loop until release:
| - 2 bytes: position | - 2 bytes: position
--- | **Game Boy extra flags** (>=106)
1 | use software envelope
1 | always init hard env on new note
``` ```
# wavetable # wavetable

View file

@ -45,8 +45,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false; #define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev105" #define DIV_VERSION "dev106"
#define DIV_ENGINE_VERSION 105 #define DIV_ENGINE_VERSION 106
// for imports // for imports
#define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_MOD 0xff01

View file

@ -590,7 +590,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
logD("GB data: vol %d dir %d len %d sl %d",ins->gb.envVol,ins->gb.envDir,ins->gb.envLen,ins->gb.soundLen); logD("GB data: vol %d dir %d len %d sl %d",ins->gb.envVol,ins->gb.envDir,ins->gb.envLen,ins->gb.soundLen);
} else if (ds.system[0]==DIV_SYSTEM_GB) { } else if (ds.system[0]==DIV_SYSTEM_GB) {
// try to convert macro to envelope // set software envelope flag
ins->gb.softEnv=true;
// try to convert macro to envelope in case the user decides to switch to them
if (ins->std.volMacro.len>0) { if (ins->std.volMacro.len>0) {
ins->gb.envVol=ins->std.volMacro.val[0]; ins->gb.envVol=ins->std.volMacro.val[0];
if (ins->std.volMacro.val[0]<ins->std.volMacro.val[1]) { if (ins->std.volMacro.val[0]<ins->std.volMacro.val[1]) {
@ -600,7 +602,6 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ins->gb.soundLen=ins->std.volMacro.len*2; ins->gb.soundLen=ins->std.volMacro.len*2;
} }
} }
addWarning("Game Boy volume macros converted to envelopes. may not be perfect!");
} }
} }

View file

@ -539,6 +539,10 @@ void DivInstrument::putInsData(SafeWriter* w) {
w->writeS(gb.hwSeq[i].data); w->writeS(gb.hwSeq[i].data);
} }
// GB additional flags
w->writeC(gb.softEnv);
w->writeC(gb.alwaysInit);
blockEndSeek=w->tell(); blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET); w->seek(blockStartSeek,SEEK_SET);
w->writeI(blockEndSeek-blockStartSeek-4); w->writeI(blockEndSeek-blockStartSeek-4);
@ -1101,6 +1105,12 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
} }
} }
// GB additional flags
if (version>=106) {
gb.softEnv=reader.readC();
gb.alwaysInit=reader.readC();
}
return DIV_DATA_SUCCESS; return DIV_DATA_SUCCESS;
} }

View file

@ -262,6 +262,7 @@ struct DivInstrumentSTD {
struct DivInstrumentGB { struct DivInstrumentGB {
unsigned char envVol, envDir, envLen, soundLen, hwSeqLen; unsigned char envVol, envDir, envLen, soundLen, hwSeqLen;
bool softEnv, alwaysInit;
enum HWSeqCommands: unsigned char { enum HWSeqCommands: unsigned char {
DIV_GB_HWCMD_ENVELOPE=0, DIV_GB_HWCMD_ENVELOPE=0,
DIV_GB_HWCMD_SWEEP, DIV_GB_HWCMD_SWEEP,
@ -281,7 +282,9 @@ struct DivInstrumentGB {
envDir(0), envDir(0),
envLen(2), envLen(2),
soundLen(64), soundLen(64),
hwSeqLen(0) { hwSeqLen(0),
softEnv(false),
alwaysInit(false) {
memset(hwSeq,0,256*sizeof(int)); memset(hwSeq,0,256*sizeof(int));
} }
}; };

View file

@ -21,8 +21,8 @@
#include "../engine.h" #include "../engine.h"
#include <math.h> #include <math.h>
#define rWrite(a,v) if (!skipRegisterWrites) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } #define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
#define immWrite(a,v) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } #define immWrite(a,v) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
#define CHIP_DIVIDER 16 #define CHIP_DIVIDER 16
@ -84,6 +84,12 @@ const char* DivPlatformGB::getEffectName(unsigned char effect) {
void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) { void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t i=start; i<start+len; i++) { for (size_t i=start; i<start+len; i++) {
if (!writes.empty()) {
QueuedWrite& w=writes.front();
GB_apu_write(gb,w.addr,w.val);
writes.pop();
}
GB_advance_cycles(gb,16); GB_advance_cycles(gb,16);
bufL[i]=gb->apu_output.final_sample.left; bufL[i]=gb->apu_output.final_sample.left;
bufR[i]=gb->apu_output.final_sample.right; bufR[i]=gb->apu_output.final_sample.right;
@ -97,8 +103,8 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len)
void DivPlatformGB::updateWave() { void DivPlatformGB::updateWave() {
rWrite(0x1a,0); rWrite(0x1a,0);
for (int i=0; i<16; i++) { for (int i=0; i<16; i++) {
int nibble1=15-ws.output[((i<<1)+antiClickWavePos)&31]; int nibble1=15-ws.output[((i<<1)+antiClickWavePos-1)&31];
int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos)&31]; int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos-1)&31];
rWrite(0x30+i,(nibble1<<4)|nibble2); rWrite(0x30+i,(nibble1<<4)|nibble2);
} }
antiClickWavePos&=31; antiClickWavePos&=31;
@ -160,6 +166,25 @@ void DivPlatformGB::tick(bool sysTick) {
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
chan[i].std.next(); chan[i].std.next();
if (chan[i].softEnv) {
if (chan[i].std.vol.had) {
chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
if (chan[i].outVol<0) chan[i].outVol=0;
if (i==2) {
rWrite(16+i*5+2,gbVolMap[chan[i].outVol]);
chan[i].soundLen=64;
} else {
chan[i].envLen=0;
chan[i].envDir=1;
chan[i].envVol=chan[i].outVol;
chan[i].soundLen=64;
if (!chan[i].keyOn) chan[i].killIt=true;
chan[i].freqChanged=true;
}
}
}
if (chan[i].std.arp.had) { if (chan[i].std.arp.had) {
if (i==3) { // noise if (i==3) { // noise
if (chan[i].std.arp.mode) { if (chan[i].std.arp.mode) {
@ -189,7 +214,7 @@ void DivPlatformGB::tick(bool sysTick) {
chan[i].duty=chan[i].std.duty.val; chan[i].duty=chan[i].std.duty.val;
if (i!=2) { if (i!=2) {
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
} else { } else if (!chan[i].softEnv) {
if (parent->song.waveDutyIsVol) { if (parent->song.waveDutyIsVol) {
rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]); rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]);
} }
@ -233,12 +258,63 @@ void DivPlatformGB::tick(bool sysTick) {
} }
} }
} }
// run hardware sequence
if (chan[i].active) {
if (--chan[i].hwSeqDelay<=0) {
chan[i].hwSeqDelay=0;
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
int hwSeqCount=0;
while (chan[i].hwSeqPos<ins->gb.hwSeqLen && hwSeqCount<4) {
bool leave=false;
unsigned short data=ins->gb.hwSeq[chan[i].hwSeqPos].data;
switch (ins->gb.hwSeq[chan[i].hwSeqPos].cmd) {
case DivInstrumentGB::DIV_GB_HWCMD_ENVELOPE:
if (!chan[i].softEnv) {
chan[i].envLen=data&7;
chan[i].envDir=(data&8)?1:0;
chan[i].envVol=(data>>4)&15;
chan[i].soundLen=data>>8;
chan[i].keyOn=true;
}
break;
case DivInstrumentGB::DIV_GB_HWCMD_SWEEP:
chan[i].sweep=data;
chan[i].sweepChanged=true;
break;
case DivInstrumentGB::DIV_GB_HWCMD_WAIT:
chan[i].hwSeqDelay=data+1;
leave=true;
break;
case DivInstrumentGB::DIV_GB_HWCMD_WAIT_REL:
if (!chan[i].released) {
chan[i].hwSeqPos--;
leave=true;
}
break;
case DivInstrumentGB::DIV_GB_HWCMD_LOOP:
chan[i].hwSeqPos=data-1;
break;
case DivInstrumentGB::DIV_GB_HWCMD_LOOP_REL:
if (!chan[i].released) {
chan[i].hwSeqPos=data-1;
}
break;
}
chan[i].hwSeqPos++;
if (leave) break;
hwSeqCount++;
}
}
}
if (chan[i].sweepChanged) { if (chan[i].sweepChanged) {
chan[i].sweepChanged=false; chan[i].sweepChanged=false;
if (i==0) { if (i==0) {
rWrite(16+i*5,chan[i].sweep); rWrite(16+i*5,chan[i].sweep);
} }
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
if (i==3) { // noise if (i==3) { // noise
int ntPos=chan[i].baseFreq; int ntPos=chan[i].baseFreq;
@ -253,10 +329,11 @@ void DivPlatformGB::tick(bool sysTick) {
if (chan[i].keyOn) { if (chan[i].keyOn) {
if (i==2) { // wave if (i==2) { // wave
rWrite(16+i*5,0x80); rWrite(16+i*5,0x80);
rWrite(16+i*5+2,gbVolMap[chan[i].vol]); rWrite(16+i*5+2,gbVolMap[chan[i].outVol]);
} else { } else {
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
rWrite(16+i*5+2,((chan[i].vol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3)); rWrite(16+i*5+2,((chan[i].envVol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3));
chan[i].lastKill=chan[i].envVol;
} }
} }
if (chan[i].keyOff) { if (chan[i].keyOff) {
@ -276,6 +353,25 @@ void DivPlatformGB::tick(bool sysTick) {
if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOn) chan[i].keyOn=false;
if (chan[i].keyOff) chan[i].keyOff=false; if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false; chan[i].freqChanged=false;
if (chan[i].killIt) {
if (i!=2) {
//rWrite(16+i*5+2,8);
int killDelta=chan[i].lastKill-chan[i].outVol+1;
if (killDelta<0) killDelta+=16;
chan[i].lastKill=chan[i].outVol;
if (killDelta!=1) {
rWrite(16+i*5+2,((chan[i].envVol<<4))|8);
for (int j=0; j<killDelta; j++) {
rWrite(16+i*5+2,0x09);
rWrite(16+i*5+2,0x11);
rWrite(16+i*5+2,0x08);
}
}
}
chan[i].killIt=false;
}
} }
} }
} }
@ -300,6 +396,10 @@ int DivPlatformGB::dispatch(DivCommand c) {
} }
chan[c.chan].active=true; chan[c.chan].active=true;
chan[c.chan].keyOn=true; chan[c.chan].keyOn=true;
chan[c.chan].hwSeqPos=0;
chan[c.chan].hwSeqDelay=0;
chan[c.chan].released=false;
chan[c.chan].softEnv=ins->gb.softEnv;
chan[c.chan].macroInit(ins); chan[c.chan].macroInit(ins);
if (c.chan==2) { if (c.chan==2) {
if (chan[c.chan].wave<0) { if (chan[c.chan].wave<0) {
@ -308,23 +408,29 @@ int DivPlatformGB::dispatch(DivCommand c) {
} }
ws.init(ins,32,15,chan[c.chan].insChanged); ws.init(ins,32,15,chan[c.chan].insChanged);
} }
if (chan[c.chan].insChanged) { if ((chan[c.chan].insChanged || ins->gb.alwaysInit) && !chan[c.chan].softEnv) {
chan[c.chan].envVol=ins->gb.envVol; chan[c.chan].envVol=ins->gb.envVol;
chan[c.chan].envLen=ins->gb.envLen; chan[c.chan].envLen=ins->gb.envLen;
chan[c.chan].envDir=ins->gb.envDir; chan[c.chan].envDir=ins->gb.envDir;
chan[c.chan].soundLen=ins->gb.soundLen; chan[c.chan].soundLen=ins->gb.soundLen;
} }
if (c.chan==2 && chan[c.chan].softEnv) {
chan[c.chan].soundLen=64;
}
chan[c.chan].insChanged=false; chan[c.chan].insChanged=false;
break; break;
} }
case DIV_CMD_NOTE_OFF: case DIV_CMD_NOTE_OFF:
chan[c.chan].active=false; chan[c.chan].active=false;
chan[c.chan].keyOff=true; chan[c.chan].keyOff=true;
chan[c.chan].hwSeqPos=0;
chan[c.chan].hwSeqDelay=0;
chan[c.chan].macroInit(NULL); chan[c.chan].macroInit(NULL);
break; break;
case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE: case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release(); chan[c.chan].std.release();
chan[c.chan].released=true;
break; break;
case DIV_CMD_INSTRUMENT: case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) { if (chan[c.chan].ins!=c.value || c.value2==1) {
@ -332,21 +438,32 @@ int DivPlatformGB::dispatch(DivCommand c) {
chan[c.chan].insChanged=true; chan[c.chan].insChanged=true;
if (c.chan!=2) { if (c.chan!=2) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB);
if (!ins->gb.softEnv) {
chan[c.chan].envVol=ins->gb.envVol; chan[c.chan].envVol=ins->gb.envVol;
chan[c.chan].envLen=ins->gb.envLen; chan[c.chan].envLen=ins->gb.envLen;
chan[c.chan].envDir=ins->gb.envDir; chan[c.chan].envDir=ins->gb.envDir;
chan[c.chan].soundLen=ins->gb.soundLen; chan[c.chan].soundLen=ins->gb.soundLen;
chan[c.chan].vol=chan[c.chan].envVol; chan[c.chan].vol=chan[c.chan].envVol;
chan[c.chan].outVol=chan[c.chan].vol;
if (parent->song.gbInsAffectsEnvelope) { if (parent->song.gbInsAffectsEnvelope) {
rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(chan[c.chan].envLen&7)|((chan[c.chan].envDir&1)<<3)); rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(chan[c.chan].envLen&7)|((chan[c.chan].envDir&1)<<3));
} }
} }
} }
}
break; break;
case DIV_CMD_VOLUME: case DIV_CMD_VOLUME:
chan[c.chan].vol=c.value; chan[c.chan].vol=c.value;
chan[c.chan].outVol=c.value;
if (c.chan==2) { if (c.chan==2) {
rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].vol]); rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].outVol]);
}
if (!chan[c.chan].softEnv) {
chan[c.chan].envVol=chan[c.chan].vol;
} else if (c.chan!=2) {
chan[c.chan].envVol=chan[c.chan].vol;
if (!chan[c.chan].keyOn) chan[c.chan].killIt=true;
chan[c.chan].freqChanged=true;
} }
break; break;
case DIV_CMD_GET_VOLUME: case DIV_CMD_GET_VOLUME:
@ -481,7 +598,7 @@ void DivPlatformGB::reset() {
} }
memset(gb,0,sizeof(GB_gameboy_t)); memset(gb,0,sizeof(GB_gameboy_t));
memset(regPool,0,128); memset(regPool,0,128);
gb->model=GB_MODEL_DMG_B; gb->model=model;
GB_apu_init(gb); GB_apu_init(gb);
GB_set_sample_rate(gb,rate); GB_set_sample_rate(gb,rate);
// enable all channels // enable all channels
@ -495,10 +612,18 @@ void DivPlatformGB::reset() {
antiClickWavePos=0; antiClickWavePos=0;
} }
int DivPlatformGB::getPortaFloor(int ch) {
return 24;
}
bool DivPlatformGB::isStereo() { bool DivPlatformGB::isStereo() {
return true; return true;
} }
bool DivPlatformGB::getDCOffRequired() {
return (model==GB_MODEL_AGB);
}
void DivPlatformGB::notifyInsChange(int ins) { void DivPlatformGB::notifyInsChange(int ins) {
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
if (chan[i].ins==ins) { if (chan[i].ins==ins) {
@ -531,6 +656,20 @@ void DivPlatformGB::poke(std::vector<DivRegWrite>& wlist) {
void DivPlatformGB::setFlags(unsigned int flags) { void DivPlatformGB::setFlags(unsigned int flags) {
antiClickEnabled=!(flags&8); antiClickEnabled=!(flags&8);
switch (flags&3) {
case 0:
model=GB_MODEL_DMG_B;
break;
case 1:
model=GB_MODEL_CGB_C;
break;
case 2:
model=GB_MODEL_CGB_E;
break;
case 3:
model=GB_MODEL_AGB;
break;
}
} }
int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
@ -544,6 +683,7 @@ int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int fl
parent=p; parent=p;
dumpWrites=false; dumpWrites=false;
skipRegisterWrites=false; skipRegisterWrites=false;
model=GB_MODEL_DMG_B;
gb=new GB_gameboy_t; gb=new GB_gameboy_t;
setFlags(flags); setFlags(flags);
reset(); reset();

View file

@ -24,15 +24,17 @@
#include "../macroInt.h" #include "../macroInt.h"
#include "../waveSynth.h" #include "../waveSynth.h"
#include "sound/gb/gb.h" #include "sound/gb/gb.h"
#include <queue>
class DivPlatformGB: public DivDispatch { class DivPlatformGB: public DivDispatch {
struct Channel { struct Channel {
int freq, baseFreq, pitch, pitch2, note, ins; int freq, baseFreq, pitch, pitch2, note, ins;
unsigned char duty, sweep; unsigned char duty, sweep;
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta; bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released, softEnv, killIt;
signed char vol, outVol, wave; signed char vol, outVol, wave, lastKill;
unsigned char envVol, envDir, envLen, soundLen; unsigned char envVol, envDir, envLen, soundLen;
unsigned short hwSeqPos, hwSeqDelay; unsigned short hwSeqPos;
short hwSeqDelay;
DivMacroInt std; DivMacroInt std;
void macroInit(DivInstrument* which) { void macroInit(DivInstrument* which) {
std.init(which); std.init(which);
@ -54,9 +56,13 @@ class DivPlatformGB: public DivDispatch {
keyOn(false), keyOn(false),
keyOff(false), keyOff(false),
inPorta(false), inPorta(false),
released(false),
softEnv(false),
killIt(false),
vol(15), vol(15),
outVol(15), outVol(15),
wave(-1), wave(-1),
lastKill(0),
envVol(0), envVol(0),
envDir(0), envDir(0),
envLen(0), envLen(0),
@ -70,10 +76,17 @@ class DivPlatformGB: public DivDispatch {
bool antiClickEnabled; bool antiClickEnabled;
unsigned char lastPan; unsigned char lastPan;
DivWaveSynth ws; DivWaveSynth ws;
struct QueuedWrite {
unsigned char addr;
unsigned char val;
QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {}
};
std::queue<QueuedWrite> writes;
int antiClickPeriodCount, antiClickWavePos; int antiClickPeriodCount, antiClickWavePos;
GB_gameboy_t* gb; GB_gameboy_t* gb;
GB_model_t model;
unsigned char regPool[128]; unsigned char regPool[128];
unsigned char procMute(); unsigned char procMute();
@ -91,7 +104,9 @@ class DivPlatformGB: public DivDispatch {
void forceIns(); void forceIns();
void tick(bool sysTick=true); void tick(bool sysTick=true);
void muteChannel(int ch, bool mute); void muteChannel(int ch, bool mute);
int getPortaFloor(int ch);
bool isStereo(); bool isStereo();
bool getDCOffRequired();
void notifyInsChange(int ins); void notifyInsChange(int ins);
void notifyWaveChange(int wave); void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins); void notifyInsDeletion(void* ins);

View file

@ -115,7 +115,7 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
pce->ResetTS(0); pce->ResetTS(0);
for (int i=0; i<6; i++) { for (int i=0; i<6; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1; oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP((pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1,-32768,32767);
} }
tempL[0]=(tempL[0]>>1)+(tempL[0]>>2); tempL[0]=(tempL[0]>>1)+(tempL[0]>>2);

View file

@ -366,7 +366,7 @@ void DivPlatformQSound::tick(bool sysTick) {
rWrite(q1_reg_map[Q1V_LOOP][i], qsound_loop); rWrite(q1_reg_map[Q1V_LOOP][i], qsound_loop);
rWrite(q1_reg_map[Q1V_START][i], qsound_addr); rWrite(q1_reg_map[Q1V_START][i], qsound_addr);
rWrite(q1_reg_map[Q1V_PHASE][i], 0x8000); rWrite(q1_reg_map[Q1V_PHASE][i], 0x8000);
//logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop); logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop);
// Write sample address. Enable volume // Write sample address. Enable volume
if (!chan[i].std.vol.had) { if (!chan[i].std.vol.had) {
rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 4); rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 4);

View file

@ -562,7 +562,7 @@ struct DivSong {
linearPitch(2), linearPitch(2),
pitchSlideSpeed(4), pitchSlideSpeed(4),
loopModality(0), loopModality(0),
properNoiseLayout(false), properNoiseLayout(true),
waveDutyIsVol(false), waveDutyIsVol(false),
resetMacroOnPorta(false), resetMacroOnPorta(false),
legacyVolumeSlides(false), legacyVolumeSlides(false),

View file

@ -334,7 +334,7 @@ void putDispatchChan(void* data, int chanNum, int type) {
break; break;
} }
default: default:
ImGui::Text("Unknown system! Help!"); ImGui::Text("Unimplemented chip! Help!");
break; break;
} }
} }

View file

@ -298,7 +298,7 @@ void FurnaceGUI::drawDebug() {
} }
if (ImGui::TreeNode("Playground")) { if (ImGui::TreeNode("Playground")) {
if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0; if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0;
if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) { if (ImGui::BeginCombo("Chip",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) {
for (int i=0; i<e->song.systemLen; i++) { for (int i=0; i<e->song.systemLen; i++) {
if (ImGui::Selectable(fmt::sprintf("%d. %s",i+1,e->getSystemName(e->song.system[i])).c_str())) { if (ImGui::Selectable(fmt::sprintf("%d. %s",i+1,e->getSystemName(e->song.system[i])).c_str())) {
pgSys=i; pgSys=i;
@ -359,7 +359,7 @@ void FurnaceGUI::drawDebug() {
if (ImGui::TreeNode("Register Cheatsheet")) { if (ImGui::TreeNode("Register Cheatsheet")) {
const char** sheet=e->getRegisterSheet(pgSys); const char** sheet=e->getRegisterSheet(pgSys);
if (sheet==NULL) { if (sheet==NULL) {
ImGui::Text("no cheatsheet available for this system."); ImGui::Text("no cheatsheet available for this chip.");
} else { } else {
if (ImGui::BeginTable("RegisterSheet",2,ImGuiTableFlags_SizingFixedSame)) { if (ImGui::BeginTable("RegisterSheet",2,ImGuiTableFlags_SizingFixedSame)) {
ImGui::TableNextRow(); ImGui::TableNextRow();

View file

@ -1879,7 +1879,7 @@ void FurnaceGUI::processDrags(int dragX, int dragY) {
#define sysAddOption(x) \ #define sysAddOption(x) \
if (ImGui::MenuItem(getSystemName(x))) { \ if (ImGui::MenuItem(getSystemName(x))) { \
if (!e->addSystem(x)) { \ if (!e->addSystem(x)) { \
showError("cannot add system! ("+e->getLastError()+")"); \ showError("cannot add chip! ("+e->getLastError()+")"); \
} \ } \
updateWindowTitle(); \ updateWindowTitle(); \
} }
@ -2887,7 +2887,7 @@ bool FurnaceGUI::loop() {
if (ImGui::MenuItem("one file")) { if (ImGui::MenuItem("one file")) {
openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE);
} }
if (ImGui::MenuItem("multiple files (one per system)")) { if (ImGui::MenuItem("multiple files (one per chip)")) {
openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS); openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS);
} }
if (ImGui::MenuItem("multiple files (one per channel)")) { if (ImGui::MenuItem("multiple files (one per channel)")) {
@ -2928,7 +2928,7 @@ bool FurnaceGUI::loop() {
"pattern indexes are ordered as they appear in the song." "pattern indexes are ordered as they appear in the song."
); );
} }
ImGui::Text("systems to export:"); ImGui::Text("chips to export:");
bool hasOneAtLeast=false; bool hasOneAtLeast=false;
for (int i=0; i<e->song.systemLen; i++) { for (int i=0; i<e->song.systemLen; i++) {
int minVersion=e->minVGMVersion(e->song.system[i]); int minVersion=e->minVGMVersion(e->song.system[i]);
@ -2937,17 +2937,17 @@ bool FurnaceGUI::loop() {
ImGui::EndDisabled(); ImGui::EndDisabled();
if (minVersion>vgmExportVersion) { if (minVersion>vgmExportVersion) {
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
ImGui::SetTooltip("this system is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); ImGui::SetTooltip("this chip is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff);
} }
} else if (minVersion==0) { } else if (minVersion==0) {
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
ImGui::SetTooltip("this system is not supported by the VGM format!"); ImGui::SetTooltip("this chip is not supported by the VGM format!");
} }
} else { } else {
if (willExport[i]) hasOneAtLeast=true; if (willExport[i]) hasOneAtLeast=true;
} }
} }
ImGui::Text("select the systems you wish to export,"); ImGui::Text("select the chip you wish to export,");
ImGui::Text("but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); ImGui::Text("but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1);
if (hasOneAtLeast) { if (hasOneAtLeast) {
if (ImGui::MenuItem("click to export")) { if (ImGui::MenuItem("click to export")) {
@ -2972,14 +2972,14 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu(); ImGui::EndMenu();
} }
ImGui::Separator(); ImGui::Separator();
if (ImGui::BeginMenu("add system...")) { if (ImGui::BeginMenu("add chip...")) {
for (int j=0; availableSystems[j]; j++) { for (int j=0; availableSystems[j]; j++) {
if (!settings.hiddenSystems && (availableSystems[j]==DIV_SYSTEM_YMU759 || availableSystems[j]==DIV_SYSTEM_DUMMY)) continue; if (!settings.hiddenSystems && (availableSystems[j]==DIV_SYSTEM_YMU759 || availableSystems[j]==DIV_SYSTEM_DUMMY)) continue;
sysAddOption((DivSystem)availableSystems[j]); sysAddOption((DivSystem)availableSystems[j]);
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("configure system...")) { if (ImGui::BeginMenu("configure chip...")) {
for (int i=0; i<e->song.systemLen; i++) { for (int i=0; i<e->song.systemLen; i++) {
if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true); drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true);
@ -2988,7 +2988,7 @@ bool FurnaceGUI::loop() {
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("change system...")) { if (ImGui::BeginMenu("change chip...")) {
ImGui::Checkbox("Preserve channel positions",&preserveChanPos); ImGui::Checkbox("Preserve channel positions",&preserveChanPos);
for (int i=0; i<e->song.systemLen; i++) { for (int i=0; i<e->song.systemLen; i++) {
if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
@ -3001,12 +3001,12 @@ bool FurnaceGUI::loop() {
} }
ImGui::EndMenu(); ImGui::EndMenu();
} }
if (ImGui::BeginMenu("remove system...")) { if (ImGui::BeginMenu("remove chip...")) {
ImGui::Checkbox("Preserve channel positions",&preserveChanPos); ImGui::Checkbox("Preserve channel positions",&preserveChanPos);
for (int i=0; i<e->song.systemLen; i++) { for (int i=0; i<e->song.systemLen; i++) {
if (ImGui::MenuItem(fmt::sprintf("%d. %s##_SYSR%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { if (ImGui::MenuItem(fmt::sprintf("%d. %s##_SYSR%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
if (!e->removeSystem(i,preserveChanPos)) { if (!e->removeSystem(i,preserveChanPos)) {
showError("cannot remove system! ("+e->getLastError()+")"); showError("cannot remove chip! ("+e->getLastError()+")");
} }
} }
} }

View file

@ -2965,6 +2965,10 @@ void FurnaceGUI::drawInsEdit() {
} }
} }
if (ins->type==DIV_INS_GB) if (ImGui::BeginTabItem("Game Boy")) { if (ins->type==DIV_INS_GB) if (ImGui::BeginTabItem("Game Boy")) {
P(ImGui::Checkbox("Use software envelope",&ins->gb.softEnv));
P(ImGui::Checkbox("Initialize envelope on every note",&ins->gb.alwaysInit));
ImGui::BeginDisabled(ins->gb.softEnv);
P(CWSliderScalar("Volume",ImGuiDataType_U8,&ins->gb.envVol,&_ZERO,&_FIFTEEN)); rightClickable P(CWSliderScalar("Volume",ImGuiDataType_U8,&ins->gb.envVol,&_ZERO,&_FIFTEEN)); rightClickable
P(CWSliderScalar("Envelope Length",ImGuiDataType_U8,&ins->gb.envLen,&_ZERO,&_SEVEN)); rightClickable P(CWSliderScalar("Envelope Length",ImGuiDataType_U8,&ins->gb.envLen,&_ZERO,&_SEVEN)); rightClickable
P(CWSliderScalar("Sound Length",ImGuiDataType_U8,&ins->gb.soundLen,&_ZERO,&_SIXTY_FOUR,ins->gb.soundLen>63?"Infinity":"%d")); rightClickable P(CWSliderScalar("Sound Length",ImGuiDataType_U8,&ins->gb.soundLen,&_ZERO,&_SIXTY_FOUR,ins->gb.soundLen>63?"Infinity":"%d")); rightClickable
@ -2989,15 +2993,18 @@ void FurnaceGUI::drawInsEdit() {
ImGui::Text("Hardware Sequence"); ImGui::Text("Hardware Sequence");
ImGui::EndMenuBar(); ImGui::EndMenuBar();
if (ins->gb.hwSeqLen>0) if (ImGui::BeginTable("HWSeqList",2)) { if (ins->gb.hwSeqLen>0) if (ImGui::BeginTable("HWSeqList",3)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed);
int curFrame=0; int curFrame=0;
ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Tick"); ImGui::Text("Tick");
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Command"); ImGui::Text("Command");
ImGui::TableNextColumn();
ImGui::Text("Move/Remove");
for (int i=0; i<ins->gb.hwSeqLen; i++) { for (int i=0; i<ins->gb.hwSeqLen; i++) {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -3060,13 +3067,13 @@ void FurnaceGUI::drawInsEdit() {
somethingChanged=true; somethingChanged=true;
} }
if (ImGui::RadioButton("Up",hwsDir)) { PARAMETER if (ImGui::RadioButton("Up",!hwsDir)) { PARAMETER
hwsDir=true; hwsDir=false;
somethingChanged=true; somethingChanged=true;
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::RadioButton("Down",!hwsDir)) { PARAMETER if (ImGui::RadioButton("Down",hwsDir)) { PARAMETER
hwsDir=false; hwsDir=true;
somethingChanged=true; somethingChanged=true;
} }
@ -3115,6 +3122,46 @@ void FurnaceGUI::drawInsEdit() {
break; break;
} }
ImGui::PopID(); ImGui::PopID();
ImGui::TableNextColumn();
ImGui::PushID(i+512);
if (ImGui::Button(ICON_FA_CHEVRON_UP "##HWCmdUp")) {
if (i>0) {
e->lockEngine([ins,i]() {
ins->gb.hwSeq[i-1].cmd^=ins->gb.hwSeq[i].cmd;
ins->gb.hwSeq[i].cmd^=ins->gb.hwSeq[i-1].cmd;
ins->gb.hwSeq[i-1].cmd^=ins->gb.hwSeq[i].cmd;
ins->gb.hwSeq[i-1].data^=ins->gb.hwSeq[i].data;
ins->gb.hwSeq[i].data^=ins->gb.hwSeq[i-1].data;
ins->gb.hwSeq[i-1].data^=ins->gb.hwSeq[i].data;
});
}
MARK_MODIFIED;
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_CHEVRON_DOWN "##HWCmdDown")) {
if (i<ins->gb.hwSeqLen-1) {
e->lockEngine([ins,i]() {
ins->gb.hwSeq[i-1].cmd^=ins->gb.hwSeq[i].cmd;
ins->gb.hwSeq[i].cmd^=ins->gb.hwSeq[i-1].cmd;
ins->gb.hwSeq[i-1].cmd^=ins->gb.hwSeq[i].cmd;
ins->gb.hwSeq[i-1].data^=ins->gb.hwSeq[i].data;
ins->gb.hwSeq[i].data^=ins->gb.hwSeq[i-1].data;
ins->gb.hwSeq[i-1].data^=ins->gb.hwSeq[i].data;
});
}
MARK_MODIFIED;
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_TIMES "##HWCmdDel")) {
for (int j=i; j<ins->gb.hwSeqLen-1; j++) {
ins->gb.hwSeq[j].cmd=ins->gb.hwSeq[j+1].cmd;
ins->gb.hwSeq[j].data=ins->gb.hwSeq[j+1].data;
}
ins->gb.hwSeqLen--;
}
ImGui::PopID();
} }
ImGui::EndTable(); ImGui::EndTable();
} }
@ -3128,6 +3175,7 @@ void FurnaceGUI::drawInsEdit() {
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::EndDisabled();
} }
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
@ -3689,8 +3737,10 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU) { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU) {
volMax=127; volMax=127;
} }
if (ins->type==DIV_INS_GB) { if (ins->type==DIV_INS_GB && !ins->gb.softEnv) {
volMax=0; volMax=0;
} else {
volMax=15;
} }
if (ins->type==DIV_INS_PET || ins->type==DIV_INS_BEEPER) { if (ins->type==DIV_INS_PET || ins->type==DIV_INS_BEEPER) {
volMax=1; volMax=1;

View file

@ -468,7 +468,7 @@ void FurnaceGUI::drawSettings() {
} }
bool restartOnFlagChangeB=settings.restartOnFlagChange; bool restartOnFlagChangeB=settings.restartOnFlagChange;
if (ImGui::Checkbox("Restart song when changing system properties",&restartOnFlagChangeB)) { if (ImGui::Checkbox("Restart song when changing chip properties",&restartOnFlagChangeB)) {
settings.restartOnFlagChange=restartOnFlagChangeB; settings.restartOnFlagChange=restartOnFlagChangeB;
} }
@ -1232,11 +1232,6 @@ void FurnaceGUI::drawSettings() {
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
bool chipNamesB=settings.chipNames;
if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) {
settings.chipNames=chipNamesB;
}
bool oplStandardWaveNamesB=settings.oplStandardWaveNames; bool oplStandardWaveNamesB=settings.oplStandardWaveNames;
if (ImGui::Checkbox("Use standard OPL waveform names",&oplStandardWaveNamesB)) { if (ImGui::Checkbox("Use standard OPL waveform names",&oplStandardWaveNamesB)) {
settings.oplStandardWaveNames=oplStandardWaveNamesB; settings.oplStandardWaveNames=oplStandardWaveNamesB;
@ -1567,8 +1562,8 @@ void FurnaceGUI::drawSettings() {
UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SONG,"Song effect"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SONG,"Song effect");
UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_TIME,"Time effect"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_TIME,"Time effect");
UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SPEED,"Speed effect"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SPEED,"Speed effect");
UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,"Primary system effect"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,"Primary specific effect");
UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,"Secondary system effect"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,"Secondary specific effect");
UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_MISC,"Miscellaneous"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_MISC,"Miscellaneous");
UI_COLOR_CONFIG(GUI_COLOR_EE_VALUE,"External command output"); UI_COLOR_CONFIG(GUI_COLOR_EE_VALUE,"External command output");
ImGui::TreePop(); ImGui::TreePop();

View file

@ -185,6 +185,19 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
if (ImGui::Checkbox("Disable anti-click",&antiClick)) { if (ImGui::Checkbox("Disable anti-click",&antiClick)) {
copyOfFlags=(flags&(~8))|(antiClick<<3); copyOfFlags=(flags&(~8))|(antiClick<<3);
} }
ImGui::Text("Chip revision:");
if (ImGui::RadioButton("Original (DMG)",(flags&7)==0)) {
copyOfFlags=(flags&(~7))|0;
}
if (ImGui::RadioButton("Game Boy Color (rev C)",(flags&7)==1)) {
copyOfFlags=(flags&(~7))|1;
}
if (ImGui::RadioButton("Game Boy Color (rev E)",(flags&7)==2)) {
copyOfFlags=(flags&(~7))|2;
}
if (ImGui::RadioButton("Game Boy Advance",(flags&7)==3)) {
copyOfFlags=(flags&(~7))|3;
}
break; break;
} }
case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL: