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
- Game Boy envelope macro/sequence
- volume commands should work on Game Boy
- ability to customize `OFF`, `===` and `REL`
- stereo separation control for AY

View file

@ -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:

View file

@ -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

View file

@ -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

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);
} 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!");
}
}

View file

@ -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;
}

View file

@ -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));
}
};

View file

@ -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();

View file

@ -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);

View file

@ -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);

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_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);

View file

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

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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()+")");
}
}
}

View file

@ -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;

View file

@ -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();

View file

@ -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: