mirror of
https://github.com/tildearrow/furnace.git
synced 2024-12-19 15:00:22 +00:00
Merge branch 'master' of https://github.com/tildearrow/furnace into sample_macro
This commit is contained in:
commit
7d83cbb7d6
18 changed files with 290 additions and 58 deletions
1
TODO.md
1
TODO.md
|
@ -1,6 +1,5 @@
|
|||
# to-do for 0.6pre1.5-0.6pre2
|
||||
|
||||
- Game Boy envelope macro/sequence
|
||||
- volume commands should work on Game Boy
|
||||
- ability to customize `OFF`, `===` and `REL`
|
||||
- stereo separation control for AY
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# 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.
|
||||
|
||||
it adds these features on top of the YM2151:
|
||||
|
|
|
@ -32,7 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
|
|||
|
||||
the format versions are:
|
||||
|
||||
- 105: Furance dev105
|
||||
- 106: Furnace dev106
|
||||
- 105: Furnace dev105
|
||||
- 104: Furnace dev104
|
||||
- 103: Furnace dev103
|
||||
- 102: Furnace 0.6pre1 (dev102)
|
||||
|
@ -846,6 +847,9 @@ size | description
|
|||
| - 2 bytes: nothing
|
||||
| - for loop/loop until release:
|
||||
| - 2 bytes: position
|
||||
--- | **Game Boy extra flags** (>=106)
|
||||
1 | use software envelope
|
||||
1 | always init hard env on new note
|
||||
```
|
||||
|
||||
# wavetable
|
||||
|
|
|
@ -45,8 +45,8 @@
|
|||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||
|
||||
#define DIV_VERSION "dev105"
|
||||
#define DIV_ENGINE_VERSION 105
|
||||
#define DIV_VERSION "dev106"
|
||||
#define DIV_ENGINE_VERSION 106
|
||||
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
|
|
|
@ -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);
|
||||
} 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) {
|
||||
ins->gb.envVol=ins->std.volMacro.val[0];
|
||||
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;
|
||||
}
|
||||
}
|
||||
addWarning("Game Boy volume macros converted to envelopes. may not be perfect!");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -539,6 +539,10 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
w->writeS(gb.hwSeq[i].data);
|
||||
}
|
||||
|
||||
// GB additional flags
|
||||
w->writeC(gb.softEnv);
|
||||
w->writeC(gb.alwaysInit);
|
||||
|
||||
blockEndSeek=w->tell();
|
||||
w->seek(blockStartSeek,SEEK_SET);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -262,6 +262,7 @@ struct DivInstrumentSTD {
|
|||
|
||||
struct DivInstrumentGB {
|
||||
unsigned char envVol, envDir, envLen, soundLen, hwSeqLen;
|
||||
bool softEnv, alwaysInit;
|
||||
enum HWSeqCommands: unsigned char {
|
||||
DIV_GB_HWCMD_ENVELOPE=0,
|
||||
DIV_GB_HWCMD_SWEEP,
|
||||
|
@ -281,7 +282,9 @@ struct DivInstrumentGB {
|
|||
envDir(0),
|
||||
envLen(2),
|
||||
soundLen(64),
|
||||
hwSeqLen(0) {
|
||||
hwSeqLen(0),
|
||||
softEnv(false),
|
||||
alwaysInit(false) {
|
||||
memset(hwSeq,0,256*sizeof(int));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
#include "../engine.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 immWrite(a,v) {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) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
#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) {
|
||||
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);
|
||||
bufL[i]=gb->apu_output.final_sample.left;
|
||||
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() {
|
||||
rWrite(0x1a,0);
|
||||
for (int i=0; i<16; i++) {
|
||||
int nibble1=15-ws.output[((i<<1)+antiClickWavePos)&31];
|
||||
int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos)&31];
|
||||
int nibble1=15-ws.output[((i<<1)+antiClickWavePos-1)&31];
|
||||
int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos-1)&31];
|
||||
rWrite(0x30+i,(nibble1<<4)|nibble2);
|
||||
}
|
||||
antiClickWavePos&=31;
|
||||
|
@ -160,6 +166,25 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
|
||||
for (int i=0; i<4; i++) {
|
||||
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 (i==3) { // noise
|
||||
if (chan[i].std.arp.mode) {
|
||||
|
@ -189,7 +214,7 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
chan[i].duty=chan[i].std.duty.val;
|
||||
if (i!=2) {
|
||||
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) {
|
||||
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) {
|
||||
chan[i].sweepChanged=false;
|
||||
if (i==0) {
|
||||
rWrite(16+i*5,chan[i].sweep);
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
if (i==3) { // noise
|
||||
int ntPos=chan[i].baseFreq;
|
||||
|
@ -253,10 +329,11 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
if (chan[i].keyOn) {
|
||||
if (i==2) { // wave
|
||||
rWrite(16+i*5,0x80);
|
||||
rWrite(16+i*5+2,gbVolMap[chan[i].vol]);
|
||||
rWrite(16+i*5+2,gbVolMap[chan[i].outVol]);
|
||||
} else {
|
||||
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) {
|
||||
|
@ -276,6 +353,25 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=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].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);
|
||||
if (c.chan==2) {
|
||||
if (chan[c.chan].wave<0) {
|
||||
|
@ -308,23 +408,29 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
}
|
||||
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].envLen=ins->gb.envLen;
|
||||
chan[c.chan].envDir=ins->gb.envDir;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].hwSeqPos=0;
|
||||
chan[c.chan].hwSeqDelay=0;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
chan[c.chan].released=true;
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
|
@ -332,21 +438,32 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
chan[c.chan].insChanged=true;
|
||||
if (c.chan!=2) {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB);
|
||||
chan[c.chan].envVol=ins->gb.envVol;
|
||||
chan[c.chan].envLen=ins->gb.envLen;
|
||||
chan[c.chan].envDir=ins->gb.envDir;
|
||||
chan[c.chan].soundLen=ins->gb.soundLen;
|
||||
chan[c.chan].vol=chan[c.chan].envVol;
|
||||
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));
|
||||
if (!ins->gb.softEnv) {
|
||||
chan[c.chan].envVol=ins->gb.envVol;
|
||||
chan[c.chan].envLen=ins->gb.envLen;
|
||||
chan[c.chan].envDir=ins->gb.envDir;
|
||||
chan[c.chan].soundLen=ins->gb.soundLen;
|
||||
chan[c.chan].vol=chan[c.chan].envVol;
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
chan[c.chan].vol=c.value;
|
||||
chan[c.chan].outVol=c.value;
|
||||
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;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
|
@ -481,7 +598,7 @@ void DivPlatformGB::reset() {
|
|||
}
|
||||
memset(gb,0,sizeof(GB_gameboy_t));
|
||||
memset(regPool,0,128);
|
||||
gb->model=GB_MODEL_DMG_B;
|
||||
gb->model=model;
|
||||
GB_apu_init(gb);
|
||||
GB_set_sample_rate(gb,rate);
|
||||
// enable all channels
|
||||
|
@ -495,10 +612,18 @@ void DivPlatformGB::reset() {
|
|||
antiClickWavePos=0;
|
||||
}
|
||||
|
||||
int DivPlatformGB::getPortaFloor(int ch) {
|
||||
return 24;
|
||||
}
|
||||
|
||||
bool DivPlatformGB::isStereo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DivPlatformGB::getDCOffRequired() {
|
||||
return (model==GB_MODEL_AGB);
|
||||
}
|
||||
|
||||
void DivPlatformGB::notifyInsChange(int ins) {
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
|
@ -531,6 +656,20 @@ void DivPlatformGB::poke(std::vector<DivRegWrite>& wlist) {
|
|||
|
||||
void DivPlatformGB::setFlags(unsigned int flags) {
|
||||
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) {
|
||||
|
@ -544,6 +683,7 @@ int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int fl
|
|||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
model=GB_MODEL_DMG_B;
|
||||
gb=new GB_gameboy_t;
|
||||
setFlags(flags);
|
||||
reset();
|
||||
|
|
|
@ -24,15 +24,17 @@
|
|||
#include "../macroInt.h"
|
||||
#include "../waveSynth.h"
|
||||
#include "sound/gb/gb.h"
|
||||
#include <queue>
|
||||
|
||||
class DivPlatformGB: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
unsigned char duty, sweep;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta;
|
||||
signed char vol, outVol, wave;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released, softEnv, killIt;
|
||||
signed char vol, outVol, wave, lastKill;
|
||||
unsigned char envVol, envDir, envLen, soundLen;
|
||||
unsigned short hwSeqPos, hwSeqDelay;
|
||||
unsigned short hwSeqPos;
|
||||
short hwSeqDelay;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
|
@ -54,9 +56,13 @@ class DivPlatformGB: public DivDispatch {
|
|||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
released(false),
|
||||
softEnv(false),
|
||||
killIt(false),
|
||||
vol(15),
|
||||
outVol(15),
|
||||
wave(-1),
|
||||
lastKill(0),
|
||||
envVol(0),
|
||||
envDir(0),
|
||||
envLen(0),
|
||||
|
@ -70,10 +76,17 @@ class DivPlatformGB: public DivDispatch {
|
|||
bool antiClickEnabled;
|
||||
unsigned char lastPan;
|
||||
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;
|
||||
|
||||
GB_gameboy_t* gb;
|
||||
GB_model_t model;
|
||||
unsigned char regPool[128];
|
||||
|
||||
unsigned char procMute();
|
||||
|
@ -91,7 +104,9 @@ class DivPlatformGB: public DivDispatch {
|
|||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
int getPortaFloor(int ch);
|
||||
bool isStereo();
|
||||
bool getDCOffRequired();
|
||||
void notifyInsChange(int ins);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
|
|
|
@ -115,7 +115,7 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
pce->ResetTS(0);
|
||||
|
||||
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);
|
||||
|
|
|
@ -366,7 +366,7 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
rWrite(q1_reg_map[Q1V_LOOP][i], qsound_loop);
|
||||
rWrite(q1_reg_map[Q1V_START][i], qsound_addr);
|
||||
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
|
||||
if (!chan[i].std.vol.had) {
|
||||
rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 4);
|
||||
|
|
|
@ -562,7 +562,7 @@ struct DivSong {
|
|||
linearPitch(2),
|
||||
pitchSlideSpeed(4),
|
||||
loopModality(0),
|
||||
properNoiseLayout(false),
|
||||
properNoiseLayout(true),
|
||||
waveDutyIsVol(false),
|
||||
resetMacroOnPorta(false),
|
||||
legacyVolumeSlides(false),
|
||||
|
|
|
@ -334,7 +334,7 @@ void putDispatchChan(void* data, int chanNum, int type) {
|
|||
break;
|
||||
}
|
||||
default:
|
||||
ImGui::Text("Unknown system! Help!");
|
||||
ImGui::Text("Unimplemented chip! Help!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -298,7 +298,7 @@ void FurnaceGUI::drawDebug() {
|
|||
}
|
||||
if (ImGui::TreeNode("Playground")) {
|
||||
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++) {
|
||||
if (ImGui::Selectable(fmt::sprintf("%d. %s",i+1,e->getSystemName(e->song.system[i])).c_str())) {
|
||||
pgSys=i;
|
||||
|
@ -359,7 +359,7 @@ void FurnaceGUI::drawDebug() {
|
|||
if (ImGui::TreeNode("Register Cheatsheet")) {
|
||||
const char** sheet=e->getRegisterSheet(pgSys);
|
||||
if (sheet==NULL) {
|
||||
ImGui::Text("no cheatsheet available for this system.");
|
||||
ImGui::Text("no cheatsheet available for this chip.");
|
||||
} else {
|
||||
if (ImGui::BeginTable("RegisterSheet",2,ImGuiTableFlags_SizingFixedSame)) {
|
||||
ImGui::TableNextRow();
|
||||
|
|
|
@ -1879,7 +1879,7 @@ void FurnaceGUI::processDrags(int dragX, int dragY) {
|
|||
#define sysAddOption(x) \
|
||||
if (ImGui::MenuItem(getSystemName(x))) { \
|
||||
if (!e->addSystem(x)) { \
|
||||
showError("cannot add system! ("+e->getLastError()+")"); \
|
||||
showError("cannot add chip! ("+e->getLastError()+")"); \
|
||||
} \
|
||||
updateWindowTitle(); \
|
||||
}
|
||||
|
@ -2887,7 +2887,7 @@ bool FurnaceGUI::loop() {
|
|||
if (ImGui::MenuItem("one file")) {
|
||||
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);
|
||||
}
|
||||
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."
|
||||
);
|
||||
}
|
||||
ImGui::Text("systems to export:");
|
||||
ImGui::Text("chips to export:");
|
||||
bool hasOneAtLeast=false;
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
int minVersion=e->minVGMVersion(e->song.system[i]);
|
||||
|
@ -2937,17 +2937,17 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::EndDisabled();
|
||||
if (minVersion>vgmExportVersion) {
|
||||
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) {
|
||||
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 {
|
||||
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);
|
||||
if (hasOneAtLeast) {
|
||||
if (ImGui::MenuItem("click to export")) {
|
||||
|
@ -2972,14 +2972,14 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginMenu("add system...")) {
|
||||
if (ImGui::BeginMenu("add chip...")) {
|
||||
for (int j=0; availableSystems[j]; j++) {
|
||||
if (!settings.hiddenSystems && (availableSystems[j]==DIV_SYSTEM_YMU759 || availableSystems[j]==DIV_SYSTEM_DUMMY)) continue;
|
||||
sysAddOption((DivSystem)availableSystems[j]);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("configure system...")) {
|
||||
if (ImGui::BeginMenu("configure chip...")) {
|
||||
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())) {
|
||||
drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true);
|
||||
|
@ -2988,7 +2988,7 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("change system...")) {
|
||||
if (ImGui::BeginMenu("change chip...")) {
|
||||
ImGui::Checkbox("Preserve channel positions",&preserveChanPos);
|
||||
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())) {
|
||||
|
@ -3001,12 +3001,12 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("remove system...")) {
|
||||
if (ImGui::BeginMenu("remove chip...")) {
|
||||
ImGui::Checkbox("Preserve channel positions",&preserveChanPos);
|
||||
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 (!e->removeSystem(i,preserveChanPos)) {
|
||||
showError("cannot remove system! ("+e->getLastError()+")");
|
||||
showError("cannot remove chip! ("+e->getLastError()+")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2965,6 +2965,10 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
}
|
||||
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("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
|
||||
|
@ -2989,15 +2993,18 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::Text("Hardware Sequence");
|
||||
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("c1",ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed);
|
||||
int curFrame=0;
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Tick");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Command");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Move/Remove");
|
||||
for (int i=0; i<ins->gb.hwSeqLen; i++) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
|
@ -3060,13 +3067,13 @@ void FurnaceGUI::drawInsEdit() {
|
|||
somethingChanged=true;
|
||||
}
|
||||
|
||||
if (ImGui::RadioButton("Up",hwsDir)) { PARAMETER
|
||||
hwsDir=true;
|
||||
if (ImGui::RadioButton("Up",!hwsDir)) { PARAMETER
|
||||
hwsDir=false;
|
||||
somethingChanged=true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::RadioButton("Down",!hwsDir)) { PARAMETER
|
||||
hwsDir=false;
|
||||
if (ImGui::RadioButton("Down",hwsDir)) { PARAMETER
|
||||
hwsDir=true;
|
||||
somethingChanged=true;
|
||||
}
|
||||
|
||||
|
@ -3115,6 +3122,46 @@ void FurnaceGUI::drawInsEdit() {
|
|||
break;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
@ -3128,6 +3175,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
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) {
|
||||
volMax=127;
|
||||
}
|
||||
if (ins->type==DIV_INS_GB) {
|
||||
if (ins->type==DIV_INS_GB && !ins->gb.softEnv) {
|
||||
volMax=0;
|
||||
} else {
|
||||
volMax=15;
|
||||
}
|
||||
if (ins->type==DIV_INS_PET || ins->type==DIV_INS_BEEPER) {
|
||||
volMax=1;
|
||||
|
|
|
@ -468,7 +468,7 @@ void FurnaceGUI::drawSettings() {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1232,11 +1232,6 @@ void FurnaceGUI::drawSettings() {
|
|||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
bool chipNamesB=settings.chipNames;
|
||||
if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) {
|
||||
settings.chipNames=chipNamesB;
|
||||
}
|
||||
|
||||
bool oplStandardWaveNamesB=settings.oplStandardWaveNames;
|
||||
if (ImGui::Checkbox("Use standard OPL waveform names",&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_TIME,"Time 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_SECONDARY,"Secondary system effect");
|
||||
UI_COLOR_CONFIG(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,"Primary specific 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_EE_VALUE,"External command output");
|
||||
ImGui::TreePop();
|
||||
|
|
|
@ -185,6 +185,19 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
|
|||
if (ImGui::Checkbox("Disable anti-click",&antiClick)) {
|
||||
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;
|
||||
}
|
||||
case DIV_SYSTEM_OPLL:
|
||||
|
|
Loading…
Reference in a new issue