SNES: Get wavesynth and envelope working

No samples, inverted volumes and E/P/N yet
It's been 3 months...
This commit is contained in:
Natt Akuma 2022-09-18 17:59:58 +07:00
parent 73c6adb821
commit 7956d41f1b
6 changed files with 224 additions and 139 deletions

1
.gitignore vendored
View file

@ -8,7 +8,6 @@ winbuild/
win32build/ win32build/
macbuild/ macbuild/
linuxbuild/ linuxbuild/
webbuild/
*.swp *.swp
.cache/ .cache/
.DS_Store .DS_Store

View file

@ -558,9 +558,10 @@ void DivInstrument::putInsData(SafeWriter* w) {
w->writeC(es5506.envelope.k2Slow); w->writeC(es5506.envelope.k2Slow);
// SNES // SNES
// @tildearrow please update this
w->writeC(snes.useEnv); w->writeC(snes.useEnv);
w->writeC(snes.gainMode); w->writeC(0);
w->writeC(snes.gain); w->writeC(0);
w->writeC(snes.a); w->writeC(snes.a);
w->writeC(snes.d); w->writeC(snes.d);
w->writeC(snes.s); w->writeC(snes.s);
@ -1250,8 +1251,8 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
// SNES // SNES
if (version>=109) { if (version>=109) {
snes.useEnv=reader.readC(); snes.useEnv=reader.readC();
snes.gainMode=(DivInstrumentSNES::GainMode)reader.readC(); reader.readC();
snes.gain=reader.readC(); reader.readC();
snes.a=reader.readC(); snes.a=reader.readC();
snes.d=reader.readC(); snes.d=reader.readC();
snes.s=reader.readC(); snes.s=reader.readC();

View file

@ -501,25 +501,17 @@ struct DivInstrumentES5506 {
}; };
struct DivInstrumentSNES { struct DivInstrumentSNES {
enum GainMode: unsigned char { bool useEnv, applyFIR;
GAIN_MODE_DIRECT=0,
GAIN_MODE_DEC_LINEAR=4,
GAIN_MODE_DEC_LOG=5,
GAIN_MODE_INC_LINEAR=6,
GAIN_MODE_INC_INVLOG=7
};
bool useEnv;
GainMode gainMode;
unsigned char gain;
unsigned char a, d, s, r; unsigned char a, d, s, r;
signed char fir[8];
DivInstrumentSNES(): DivInstrumentSNES():
useEnv(true), useEnv(true),
gainMode(GAIN_MODE_DIRECT), applyFIR(false),
gain(127),
a(15), a(15),
d(7), d(7),
s(7), s(7),
r(0) {} r(0),
fir{127, 0, 0, 0, 0, 0, 0, 0} {}
}; };
struct DivInstrument { struct DivInstrument {

View file

@ -22,9 +22,12 @@
#include "../../ta-log.h" #include "../../ta-log.h"
#include <math.h> #include <math.h>
#define CHIP_FREQBASE 4096 #define CHIP_FREQBASE 131072
#define rWrite(a,v) {dsp.write(a,v); regPool[(a)&0x7f]=v; } #define rWrite(a,v) {dsp.write(a,v); regPool[(a)&0x7f]=v; }
#define chWrite(c,a,v) {rWrite((a)+(c)*16,v)}
#define sampleTableAddr(c) (sampleTableBase+(c)*4)
#define waveTableAddr(c) (sampleTableBase+8*4+(c)*9*16)
const char* regCheatSheetSNESDSP[]={ const char* regCheatSheetSNESDSP[]={
"VxVOLL", "x0", "VxVOLL", "x0",
@ -62,16 +65,6 @@ const char** DivPlatformSNES::getRegisterSheet() {
return regCheatSheetSNESDSP; return regCheatSheetSNESDSP;
} }
const char* DivPlatformSNES::getEffectName(unsigned char effect) {
switch (effect) {
case 0x10:
return "10xx: Set echo feedback level (signed 8-bit)";
break;
// TODO
}
return NULL;
}
void DivPlatformSNES::acquire(short* bufL, short* bufR, size_t start, size_t len) { void DivPlatformSNES::acquire(short* bufL, short* bufR, size_t start, size_t len) {
short out[2]; short out[2];
short chOut[16]; short chOut[16];
@ -90,12 +83,23 @@ void DivPlatformSNES::acquire(short* bufL, short* bufR, size_t start, size_t len
void DivPlatformSNES::tick(bool sysTick) { void DivPlatformSNES::tick(bool sysTick) {
// KON/KOFF can't be written several times per one sample // KON/KOFF can't be written several times per one sample
// so they have to be accumulated // so they have to be accumulated
// TODO due to pipelining, KON/KOFF writes need to be delayed to accomodate sample address changes in the table
unsigned char kon=0; unsigned char kon=0;
unsigned char koff=0; unsigned char koff=0;
for (int i=0; i<8; i++) { for (int i=0; i<8; i++) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
bool hadGain=chan[i].std.vol.had || chan[i].std.ex1.had || chan[i].std.ex2.had;
chan[i].std.next(); chan[i].std.next();
if (chan[i].std.vol.had) { if (ins->type==DIV_INS_AMIGA && chan[i].std.vol.had) {
// TODO handle gain writes chWrite(i,7,MIN(127,chan[i].std.vol.val*2));
} else if (!chan[i].useEnv && hadGain) {
if (chan[i].std.ex1.val==0) {
// direct gain
chWrite(i,7,chan[i].std.vol.val);
} else {
// inc/dec
chWrite(i,7,chan[i].std.ex2.val|((chan[i].std.ex1.val-1)<<5)|0x80);
}
} }
if (chan[i].std.arp.had) { if (chan[i].std.arp.had) {
if (!chan[i].inPorta) { if (!chan[i].inPorta) {
@ -112,6 +116,12 @@ void DivPlatformSNES::tick(bool sysTick) {
chan[i].freqChanged=true; chan[i].freqChanged=true;
} }
} }
if (chan[i].useWave && chan[i].std.wave.had) {
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave.val;
chan[i].ws.changeWave1(chan[i].wave);
}
}
if (chan[i].std.pitch.had) { if (chan[i].std.pitch.had) {
if (chan[i].std.pitch.mode) { if (chan[i].std.pitch.mode) {
chan[i].pitch2+=chan[i].std.pitch.val; chan[i].pitch2+=chan[i].std.pitch.val;
@ -139,13 +149,46 @@ void DivPlatformSNES::tick(bool sysTick) {
} else { } else {
chan[i].audPos=0; chan[i].audPos=0;
} }
if (chan[i].useWave && chan[i].active) {
if (chan[i].ws.tick()) {
updateWave(i);
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
DivSample* s=parent->getSample(chan[i].sample); DivSample* s=parent->getSample(chan[i].sample);
double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
chan[i].freq=(unsigned int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE)); chan[i].freq=(unsigned int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE));
if (chan[i].freq>16383) chan[i].freq=16383; if (chan[i].freq>16383) chan[i].freq=16383;
if (chan[i].keyOn) { if (chan[i].keyOn) {
// TODO handle sample offsets unsigned int start, end, loop;
size_t tabAddr=sampleTableAddr(i);
if (chan[i].useEnv) {
chWrite(i,5,ins->snes.a|(ins->snes.d<<4)|0x80);
chWrite(i,6,ins->snes.r|(ins->snes.s<<5));
} else {
chWrite(i,5,0);
}
if (chan[i].useWave) {
start=waveTableAddr(i);
loop=start;
} else {
start=s->offSNES;
end=MIN(start+MAX(s->lengthBRR,1),getSampleMemCapacity());
loop=MAX(start,end-1);
if (chan[i].audPos>0) {
start=start+MIN(chan[i].audPos,s->lengthBRR-1)/16*9;
}
if (s->loopStart>=0) {
loop=start+s->loopStart/16*9;
}
}
sampleMem[tabAddr+0]=start&0xff;
sampleMem[tabAddr+1]=start>>8;
sampleMem[tabAddr+2]=loop&0xff;
sampleMem[tabAddr+3]=loop>>8;
if (!hadGain) {
chWrite(i,7,0x7f);
}
kon|=(1<<i); kon|=(1<<i);
chan[i].keyOn=false; chan[i].keyOn=false;
} }
@ -154,8 +197,8 @@ void DivPlatformSNES::tick(bool sysTick) {
chan[i].keyOff=false; chan[i].keyOff=false;
} }
if (chan[i].freqChanged) { if (chan[i].freqChanged) {
rWrite(2+i*16,chan[i].freq&0xff); chWrite(i,2,chan[i].freq&0xff);
rWrite(3+i*16,chan[i].freq>>8); chWrite(i,3,chan[i].freq>>8);
chan[i].freqChanged=false; chan[i].freqChanged=false;
} }
} }
@ -168,20 +211,36 @@ int DivPlatformSNES::dispatch(DivCommand c) {
switch (c.cmd) { switch (c.cmd) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
if (ins->amiga.useWave) {
chan[c.chan].useWave=true;
chan[c.chan].wtLen=(unsigned int)(ins->amiga.waveLen)+1;
if (chan[c.chan].insChanged) {
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
chan[c.chan].ws.setWidth(chan[c.chan].wtLen);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
}
} else {
chan[c.chan].sample=ins->amiga.getSample(c.value); chan[c.chan].sample=ins->amiga.getSample(c.value);
if (c.value!=DIV_NOTE_NULL) { chan[c.chan].useWave=false;
chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value));
} }
if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1; chan[c.chan].sample=-1;
} }
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value));
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
} }
chan[c.chan].active=true; chan[c.chan].active=true;
chan[c.chan].keyOn=true; chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins); chan[c.chan].macroInit(ins);
if (ins->type==DIV_INS_SNES) {
// initialize to max gain in case of direct gain mode macro without gain level macro
chan[c.chan].std.vol.val=0x7f;
chan[c.chan].useEnv=ins->snes.useEnv;
}
chan[c.chan].insChanged=false; chan[c.chan].insChanged=false;
break; break;
} }
@ -274,6 +333,20 @@ int DivPlatformSNES::dispatch(DivCommand c) {
return 1; return 1;
} }
void DivPlatformSNES::updateWave(int ch) {
// Due to the overflow bug in hardware's resampler, the written amplitude here is half of maximum
size_t pos=waveTableAddr(ch);
for (int i=0; i<chan[ch].wtLen/16; i++) {
sampleMem[pos++]=0xb0;
for (int j=0; j<8; j++) {
int nibble1=(chan[ch].ws.output[i*16+j*2]-8)&15;
int nibble2=(chan[ch].ws.output[i*16+j*2+1]-8)&15;
sampleMem[pos++]=(nibble1<<4)|nibble2;
}
}
sampleMem[pos-9]=0xb3; // mark loop
}
void DivPlatformSNES::writeOutVol(int ch) { void DivPlatformSNES::writeOutVol(int ch) {
// TODO negative (inverted) panning support // TODO negative (inverted) panning support
int outL=0; int outL=0;
@ -282,8 +355,8 @@ void DivPlatformSNES::writeOutVol(int ch) {
outL=chan[ch].vol*chan[ch].panL/255; outL=chan[ch].vol*chan[ch].panL/255;
outR=chan[ch].vol*chan[ch].panR/255; outR=chan[ch].vol*chan[ch].panR/255;
} }
rWrite(0+ch*16,outL); chWrite(ch,0,outL);
rWrite(1+ch*16,outR); chWrite(ch,1,outR);
} }
void DivPlatformSNES::muteChannel(int ch, bool mute) { void DivPlatformSNES::muteChannel(int ch, bool mute) {
@ -296,6 +369,7 @@ void DivPlatformSNES::forceIns() {
chan[i].insChanged=true; chan[i].insChanged=true;
chan[i].freqChanged=true; chan[i].freqChanged=true;
chan[i].sample=-1; chan[i].sample=-1;
updateWave(i);
} }
} }
@ -334,10 +408,14 @@ void DivPlatformSNES::reset() {
rWrite(0x5d,sampleTableBase>>8); rWrite(0x5d,sampleTableBase>>8);
rWrite(0x0c,127); // global volume left rWrite(0x0c,127); // global volume left
rWrite(0x1c,127); // global volume right rWrite(0x1c,127); // global volume right
rWrite(0x6c,0); // get DSP out of reset
for (int i=0; i<8; i++) { for (int i=0; i<8; i++) {
chan[i]=Channel(); chan[i]=Channel();
chan[i].std.setEngine(parent); chan[i].std.setEngine(parent);
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,32,255);
writeOutVol(i); writeOutVol(i);
chWrite(i,4,i); // source number
} }
} }
@ -353,6 +431,17 @@ void DivPlatformSNES::notifyInsChange(int ins) {
} }
} }
void DivPlatformSNES::notifyWaveChange(int wave) {
for (int i=0; i<8; i++) {
if (chan[i].useWave && chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
if (chan[i].active) {
updateWave(i);
}
}
}
}
void DivPlatformSNES::notifyInsDeletion(void* ins) { void DivPlatformSNES::notifyInsDeletion(void* ins) {
for (int i=0; i<8; i++) { for (int i=0; i<8; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins); chan[i].std.notifyInsDeletion((DivInstrument*)ins);
@ -383,21 +472,14 @@ size_t DivPlatformSNES::getSampleMemUsage(int index) {
void DivPlatformSNES::renderSamples() { void DivPlatformSNES::renderSamples() {
memset(sampleMem,0,getSampleMemCapacity()); memset(sampleMem,0,getSampleMemCapacity());
// skip past sample table // skip past sample table and wavetable buffer
size_t memPos=sampleTableBase+0x400; size_t memPos=sampleTableBase+8*4+8*9*16;
for (int i=0; i<parent->song.sampleLen; i++) { for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i]; DivSample* s=parent->song.sample[i];
int length=s->lengthBRR; int length=s->lengthBRR;
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)/9*9,length); int actualLength=MIN((int)(getSampleMemCapacity()-memPos)/9*9,length);
if (actualLength>0) { if (actualLength>0) {
size_t tabAddr=sampleTableBase+i*4;
size_t loopPos=memPos;
if (s->loopStart>=0) loopPos+=s->loopStart;
s->offSNES=memPos; s->offSNES=memPos;
sampleMem[tabAddr+0]=memPos&0xff;
sampleMem[tabAddr+1]=memPos>>8;
sampleMem[tabAddr+2]=loopPos&0xff;
sampleMem[tabAddr+3]=loopPos>>8;
memcpy(&sampleMem[memPos],s->data8,actualLength); memcpy(&sampleMem[memPos],s->data8,actualLength);
memPos+=actualLength; memPos+=actualLength;
} }

View file

@ -22,19 +22,22 @@
#include "../dispatch.h" #include "../dispatch.h"
#include "../macroInt.h" #include "../macroInt.h"
#include "../waveSynth.h"
#include <queue> #include <queue>
#include "sound/snes/SPC_DSP.h" #include "sound/snes/SPC_DSP.h"
class DivPlatformSNES: public DivDispatch { class DivPlatformSNES: public DivDispatch {
struct Channel { struct Channel {
int freq, baseFreq, pitch, pitch2; int freq, baseFreq, pitch, pitch2;
unsigned int audPos; unsigned int audPos, wtLen;
int sample, ins; int sample, wave, ins;
int note; int note;
int panL, panR; int panL, panR;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos;
signed char vol; signed char vol;
bool useEnv;
DivMacroInt std; DivMacroInt std;
DivWaveSynth ws;
void macroInit(DivInstrument* which) { void macroInit(DivInstrument* which) {
std.init(which); std.init(which);
pitch2=0; pitch2=0;
@ -45,7 +48,9 @@ class DivPlatformSNES: public DivDispatch {
pitch(0), pitch(0),
pitch2(0), pitch2(0),
audPos(0), audPos(0),
wtLen(16),
sample(-1), sample(-1),
wave(-1),
ins(-1), ins(-1),
note(0), note(0),
panL(255), panL(255),
@ -58,7 +63,8 @@ class DivPlatformSNES: public DivDispatch {
inPorta(false), inPorta(false),
useWave(false), useWave(false),
setPos(false), setPos(false),
vol(127) {} vol(127),
useEnv(false) {}
}; };
Channel chan[8]; Channel chan[8];
DivDispatchOscBuffer* oscBuf[8]; DivDispatchOscBuffer* oscBuf[8];
@ -86,11 +92,11 @@ class DivPlatformSNES: public DivDispatch {
void muteChannel(int ch, bool mute); void muteChannel(int ch, bool mute);
bool isStereo(); bool isStereo();
void notifyInsChange(int ins); void notifyInsChange(int ins);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins); void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val); void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist); void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet(); const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
const void* getSampleMem(int index = 0); const void* getSampleMem(int index = 0);
size_t getSampleMemCapacity(int index = 0); size_t getSampleMemCapacity(int index = 0);
size_t getSampleMemUsage(int index = 0); size_t getSampleMemUsage(int index = 0);
@ -98,6 +104,7 @@ class DivPlatformSNES: public DivDispatch {
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit(); void quit();
private: private:
void updateWave(int ch);
void writeOutVol(int ch); void writeOutVol(int ch);
}; };

View file

@ -306,6 +306,14 @@ const char* gbHWSeqCmdTypes[6]={
"Loop until Release" "Loop until Release"
}; };
const char* snesGainModes[5]={
"Direct",
"Decrease (linear)",
"Decrease (logarithmic)",
"Increase (linear)",
"Increase (bent line)"
};
// do not change these! // do not change these!
// anything other than a checkbox will look ugly! // anything other than a checkbox will look ugly!
// //
@ -3603,6 +3611,18 @@ void FurnaceGUI::drawInsEdit() {
} }
} }
} }
if (ins->type==DIV_INS_SNES) {
P(ImGui::Checkbox("Use wavetable",&ins->amiga.useWave));
if (ins->amiga.useWave) {
int len=ins->amiga.waveLen+1;
if (ImGui::InputInt("Width",&len,16,64)) {
if (len<16) len=16;
if (len>256) len=256;
ins->amiga.waveLen=(len&(~15))-1;
PARAMETER
}
}
}
ImGui::BeginDisabled(ins->amiga.useWave); ImGui::BeginDisabled(ins->amiga.useWave);
P(ImGui::Checkbox("Use sample map (does not work yet!)",&ins->amiga.useNoteMap)); P(ImGui::Checkbox("Use sample map (does not work yet!)",&ins->amiga.useNoteMap));
if (ins->amiga.useNoteMap) { if (ins->amiga.useNoteMap) {
@ -3868,7 +3888,6 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_SNES) if (ImGui::BeginTabItem("SNES")) { if (ins->type==DIV_INS_SNES) if (ImGui::BeginTabItem("SNES")) {
P(ImGui::Checkbox("Use envelope",&ins->snes.useEnv)); P(ImGui::Checkbox("Use envelope",&ins->snes.useEnv));
ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale);
if (ins->snes.useEnv) {
if (ImGui::BeginTable("SNESEnvParams",5,ImGuiTableFlags_NoHostExtendX)) { if (ImGui::BeginTable("SNESEnvParams",5,ImGuiTableFlags_NoHostExtendX)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
@ -3907,56 +3926,24 @@ void FurnaceGUI::drawInsEdit() {
ImGui::EndTable(); ImGui::EndTable();
} }
} else { P(ImGui::Checkbox("Apply echo filter",&ins->snes.applyFIR));
if (ImGui::BeginTable("SNESGainParams",3,ImGuiTableFlags_NoHostExtendX)) { if (ins->snes.applyFIR) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); double inBuf[8];
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); fftw_complex outBuf[8];
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); float curve[5];
fftw_plan plan=fftw_plan_dft_r2c_1d(8,inBuf,outBuf,FFTW_ESTIMATE);
ImGui::TableNextRow(); ImGui::Text("Coefficients");
ImGui::TableNextColumn(); ImGui::SameLine();
CENTER_TEXT("Gain Mode"); P(ImGui::DragScalarN("##FIRCoeff",ImGuiDataType_S8,ins->snes.fir,8,1,&_MINUS_ONE_HUNDRED_TWENTY_EIGHT,&_ONE_HUNDRED_TWENTY_SEVEN)); rightClickable
ImGui::TextUnformatted("Gain Mode"); for (int i=0; i<8; i++) {
ImGui::TableNextColumn(); inBuf[i] = ins->snes.fir[i];
CENTER_TEXT("Gain");
ImGui::TextUnformatted("Gain");
ImGui::TableNextColumn();
CENTER_TEXT("Envelope");
ImGui::TextUnformatted("Envelope");
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::RadioButton("Direct",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)) {
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DIRECT;
PARAMETER;
} }
if (ImGui::RadioButton("Decrease (linear)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LINEAR)) { fftw_execute(plan);
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LINEAR; for (int i=0; i<5; i++) {
PARAMETER; curve[i] = sqrtf(powf(outBuf[i][0],2)+powf(outBuf[i][1],2))/128.f;
}
if (ImGui::RadioButton("Decrease (logarithmic)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LOG)) {
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LOG;
PARAMETER;
}
if (ImGui::RadioButton("Increase (linear)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_INC_LINEAR)) {
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_INC_LINEAR;
PARAMETER;
}
if (ImGui::RadioButton("Increase (bent line)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_INC_INVLOG)) {
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_INC_INVLOG;
PARAMETER;
}
ImGui::TableNextColumn();
unsigned char gainMax=(ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)?127:31;
if (ins->snes.gain>gainMax) ins->snes.gain=gainMax;
P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax));
ImGui::TableNextColumn();
ImGui::Text("Envelope goes here...");
ImGui::EndTable();
} }
ImGui::PlotLines("##FIRResponse",curve,5,0,"Frequency response",0.0,8.0,ImVec2(ImGui::GetContentRegionAvail().x,100.0f*dpiScale));
} }
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
@ -4159,6 +4146,14 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_ES5506) { if (ins->type==DIV_INS_ES5506) {
volMax=65535; volMax=65535;
} }
if (ins->type==DIV_INS_SNES) {
if (ins->snes.useEnv) {
volMax=0;
} else {
volumeLabel="Gain Level";
volMax=127;
}
}
const char* dutyLabel="Duty/Noise"; const char* dutyLabel="Duty/Noise";
int dutyMin=0; int dutyMin=0;
@ -4203,7 +4198,7 @@ void FurnaceGUI::drawInsEdit() {
dutyLabel="Noise"; dutyLabel="Noise";
dutyMax=8; dutyMax=8;
} }
if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS || ins->type==DIV_INS_MULTIPCM) { if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SNES) {
dutyMax=0; dutyMax=0;
} }
if (ins->type==DIV_INS_VERA) { if (ins->type==DIV_INS_VERA) {
@ -4284,6 +4279,10 @@ void FurnaceGUI::drawInsEdit() {
ex1Max=65535; ex1Max=65535;
ex2Max=65535; ex2Max=65535;
} }
if (ins->type==DIV_INS_SNES && !ins->snes.useEnv) {
ex1Max=4;
ex2Max=31;
}
int panMin=0; int panMin=0;
int panMax=0; int panMax=0;
@ -4373,7 +4372,8 @@ void FurnaceGUI::drawInsEdit() {
ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_MULTIPCM ||
ins->type==DIV_INS_SU || ins->type==DIV_INS_SU ||
ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MIKEY ||
ins->type==DIV_INS_ES5506) { ins->type==DIV_INS_ES5506 ||
ins->type==DIV_INS_SNES) {
macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); macroList.push_back(FurnaceGUIMacroDesc("Phase Reset",&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
} }
if (ex1Max>0) { if (ex1Max>0) {
@ -4391,6 +4391,8 @@ void FurnaceGUI::drawInsEdit() {
macroList.push_back(FurnaceGUIMacroDesc("Cutoff",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("Cutoff",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} else if (ins->type==DIV_INS_ES5506) { } else if (ins->type==DIV_INS_ES5506) {
macroList.push_back(FurnaceGUIMacroDesc("Filter K1",&ins->std.ex1Macro,((ins->std.ex1Macro.mode==1)?(-ex1Max):0),ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,macroRelativeMode)); macroList.push_back(FurnaceGUIMacroDesc("Filter K1",&ins->std.ex1Macro,((ins->std.ex1Macro.mode==1)?(-ex1Max):0),ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,macroRelativeMode));
} else if (ins->type==DIV_INS_SNES) {
macroList.push_back(FurnaceGUIMacroDesc("Gain Mode",&ins->std.ex1Macro,0,ex1Max,64,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,NULL,false,snesGainModes));
} else { } else {
macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} }
@ -4406,6 +4408,8 @@ void FurnaceGUI::drawInsEdit() {
macroList.push_back(FurnaceGUIMacroDesc("Resonance",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("Resonance",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} else if (ins->type==DIV_INS_ES5506) { } else if (ins->type==DIV_INS_ES5506) {
macroList.push_back(FurnaceGUIMacroDesc("Filter K2",&ins->std.ex2Macro,((ins->std.ex2Macro.mode==1)?(-ex2Max):0),ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,macroRelativeMode)); macroList.push_back(FurnaceGUIMacroDesc("Filter K2",&ins->std.ex2Macro,((ins->std.ex2Macro.mode==1)?(-ex2Max):0),ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,macroRelativeMode));
} else if (ins->type==DIV_INS_SNES) {
macroList.push_back(FurnaceGUIMacroDesc("Gain Rate",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_VOLUME]));
} else { } else {
macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex2Macro,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,ex2Bit,ayEnvBits)); macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex2Macro,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,ex2Bit,ayEnvBits));
} }