Merge branch 'master' into feature/esfm

This commit is contained in:
Kagamiin~ 2023-10-25 17:10:16 -03:00
commit 85db9ca16e
17 changed files with 478 additions and 26 deletions

11
TODO.md
View file

@ -4,13 +4,18 @@
# THE REAL TO-DO LIST # THE REAL TO-DO LIST
because I want to focus for a couple hours
- Amiga's Period Modulation not working - Amiga's Period Modulation not working
- Song is silent after playing an order after loop point - Song is silent after playing an order after loop point
- Crash when opening one-column chan osc with 48 channels
- Select loaded instrument on open - rewrite because I want a setting... - Select loaded instrument on open - rewrite because I want a setting...
- re-engineer volume handling? Sound Unit cries at me
- finish status view
- finish auto-clone
once you have done all of this (maybe not the first one) and merged the two or so pending pull requests, release 0.6.1 once you have done all of this (maybe not the first one) and merged the two or so pending pull requests, release 0.6.1
Furnace is like alcohol... Furnace is like alcohol...
# and then
- new oscilloscope renderer - custom code that uses texture and fixes two issues: too many vertices, and broken anti-aliasing
- new pattern renderer - performance improvements

View file

@ -47,6 +47,7 @@ two versions of aforementioned chip exist - 6581 (original chip) and 8580 (impro
- if `y` is not 0: now - if `y` is not 0: now
- this effect is not necessary if the instrument's duty macro is absolute. - this effect is not necessary if the instrument's duty macro is absolute.
- `1Exy`: **change additional parameters.** - `1Exy`: **change additional parameters.**
- _this effect only exists for compatibility reasons, and its use is discouraged._
- `x` may be one of the following: - `x` may be one of the following:
- `0`: attack (`y` from `0` to `F`) - `0`: attack (`y` from `0` to `F`)
- `1`: decay (`y` from `0` to `F`) - `1`: decay (`y` from `0` to `F`)
@ -55,6 +56,12 @@ two versions of aforementioned chip exist - 6581 (original chip) and 8580 (impro
- `4`: ring modulation (`y` is `0` or `1`) - `4`: ring modulation (`y` is `0` or `1`)
- `5`: oscillator sync (`y` is `0` or `1`) - `5`: oscillator sync (`y` is `0` or `1`)
- `6`: disable channel 3 (`y` is `0` or `1`) - `6`: disable channel 3 (`y` is `0` or `1`)
- `20xy`: **set attack/decay.**
- `x` is the attack.
- `y` is the decay.
- `21xy`: **set sustain/release.**
- `x` is the sustain.
- `y` is the release.
- `3xxx`: **set duty cycle.** `xxx` range is `000` to `FFF`. - `3xxx`: **set duty cycle.** `xxx` range is `000` to `FFF`.
- `4xxx`: **set cutoff.** `xxx` range is `000` to `7FF`. - `4xxx`: **set cutoff.** `xxx` range is `000` to `7FF`.

View file

@ -556,6 +556,31 @@ size | description
size | description size | description
-----|------------------------------------ -----|------------------------------------
1 | switch roles of phase reset timer and frequency 1 | switch roles of phase reset timer and frequency
1 | hardware sequence length (>=185)
??? | hardware sequence...
| - length: 5*hwSeqLen
```
a value in the hardware sequence has the following format:
```
size | description
-----|------------------------------------
1 | command
| - 0: set volume sweep
| - 1: set frequency sweep
| - 2: set cutoff sweep
| - 3: wait
| - 4: wait for release
| - 5: loop
| - 6: loop until release
1 | sweep bound
1 | sweep amount/command data
| - if "set sweep", this is amount.
| - for wait: length in ticks
| - for wait for release: nothing
| - for loop/loop until release: position
2 | sweep period
``` ```
# ES5506 data (ES) # ES5506 data (ES)

View file

@ -238,13 +238,16 @@ enum DivDispatchCmds {
DIV_CMD_EXTERNAL, // (value) DIV_CMD_EXTERNAL, // (value)
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol DIV_CMD_C64_AD, // (value)
DIV_CMD_C64_SR, // (value)
DIV_CMD_ESFM_OP_PANNING, // (op, value) DIV_CMD_ESFM_OP_PANNING, // (op, value)
DIV_CMD_ESFM_OUTLVL, // (op, value) DIV_CMD_ESFM_OUTLVL, // (op, value)
DIV_CMD_ESFM_MODIN, // (op, value) DIV_CMD_ESFM_MODIN, // (op, value)
DIV_CMD_ESFM_ENV_DELAY, // (op, value) DIV_CMD_ESFM_ENV_DELAY, // (op, value)
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
DIV_CMD_MAX DIV_CMD_MAX
}; };

View file

@ -54,8 +54,8 @@ class DivWorkPool;
#define DIV_UNSTABLE #define DIV_UNSTABLE
#define DIV_VERSION "dev184" #define DIV_VERSION "dev186"
#define DIV_ENGINE_VERSION 184 #define DIV_ENGINE_VERSION 186
// for imports // for imports
#define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02 #define DIV_VERSION_FC 0xff02

View file

@ -3008,6 +3008,15 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} }
} }
// C64 1Exy compat
if (ds.version<186) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_C64_8580 || ds.system[i]==DIV_SYSTEM_C64_6581) {
ds.systemFlags[i].set("no1EUpdate",true);
}
}
}
if (active) quitDispatch(); if (active) quitDispatch();
BUSY_BEGIN_SOFT; BUSY_BEGIN_SOFT;
saveLock.lock(); saveLock.lock();
@ -3149,8 +3158,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
ds.sampleLen=ds.sample.size(); ds.sampleLen=ds.sample.size();
// orders // orders
ds.subsong[0]->ordersLen=ordCount=reader.readC(); ds.subsong[0]->ordersLen=ordCount=(unsigned char)reader.readC();
if (ds.subsong[0]->ordersLen<1 || ds.subsong[0]->ordersLen>127) { if (ds.subsong[0]->ordersLen<1 || ds.subsong[0]->ordersLen>128) {
logD("invalid order count!"); logD("invalid order count!");
throw EndOfFileException(&reader,reader.tell()); throw EndOfFileException(&reader,reader.tell());
} }

View file

@ -196,7 +196,10 @@ bool DivInstrumentWaveSynth::operator==(const DivInstrumentWaveSynth& other) {
} }
bool DivInstrumentSoundUnit::operator==(const DivInstrumentSoundUnit& other) { bool DivInstrumentSoundUnit::operator==(const DivInstrumentSoundUnit& other) {
return _C(switchRoles); return (
_C(switchRoles) &&
_C(hwSeqLen)
);
} }
bool DivInstrumentES5506::operator==(const DivInstrumentES5506& other) { bool DivInstrumentES5506::operator==(const DivInstrumentES5506& other) {
@ -714,6 +717,14 @@ void DivInstrument::writeFeatureSU(SafeWriter* w) {
w->writeC(su.switchRoles); w->writeC(su.switchRoles);
w->writeC(su.hwSeqLen);
for (int i=0; i<su.hwSeqLen; i++) {
w->writeC(su.hwSeq[i].cmd);
w->writeC(su.hwSeq[i].bound);
w->writeC(su.hwSeq[i].val);
w->writeS(su.hwSeq[i].speed);
}
FEATURE_END; FEATURE_END;
} }
@ -2585,6 +2596,16 @@ void DivInstrument::readFeatureSU(SafeReader& reader, short version) {
su.switchRoles=reader.readC(); su.switchRoles=reader.readC();
if (version>=185) {
su.hwSeqLen=reader.readC();
for (int i=0; i<su.hwSeqLen; i++) {
su.hwSeq[i].cmd=reader.readC();
su.hwSeq[i].bound=reader.readC();
su.hwSeq[i].val=reader.readC();
su.hwSeq[i].speed=reader.readS();
}
}
READ_FEAT_END; READ_FEAT_END;
} }

View file

@ -388,7 +388,7 @@ struct DivInstrumentGB {
DIV_GB_HWCMD_MAX DIV_GB_HWCMD_MAX
}; };
struct HWSeqCommand { struct HWSeqCommandGB {
unsigned char cmd; unsigned char cmd;
unsigned short data; unsigned short data;
} hwSeq[256]; } hwSeq[256];
@ -406,7 +406,7 @@ struct DivInstrumentGB {
hwSeqLen(0), hwSeqLen(0),
softEnv(false), softEnv(false),
alwaysInit(false) { alwaysInit(false) {
memset(hwSeq,0,256*sizeof(int)); memset(hwSeq,0,256*sizeof(HWSeqCommandGB));
} }
}; };
@ -666,6 +666,25 @@ struct DivInstrumentWaveSynth {
struct DivInstrumentSoundUnit { struct DivInstrumentSoundUnit {
bool switchRoles; bool switchRoles;
unsigned char hwSeqLen;
enum HWSeqCommands: unsigned char {
DIV_SU_HWCMD_VOL=0,
DIV_SU_HWCMD_PITCH,
DIV_SU_HWCMD_CUT,
DIV_SU_HWCMD_WAIT,
DIV_SU_HWCMD_WAIT_REL,
DIV_SU_HWCMD_LOOP,
DIV_SU_HWCMD_LOOP_REL,
DIV_SU_HWCMD_MAX
};
struct HWSeqCommandSU {
unsigned char cmd;
unsigned char bound;
unsigned char val;
unsigned short speed;
unsigned short padding;
} hwSeq[256];
bool operator==(const DivInstrumentSoundUnit& other); bool operator==(const DivInstrumentSoundUnit& other);
bool operator!=(const DivInstrumentSoundUnit& other) { bool operator!=(const DivInstrumentSoundUnit& other) {
@ -673,7 +692,10 @@ struct DivInstrumentSoundUnit {
} }
DivInstrumentSoundUnit(): DivInstrumentSoundUnit():
switchRoles(false) {} switchRoles(false),
hwSeqLen(0) {
memset(hwSeq,0,256*sizeof(HWSeqCommandSU));
}
}; };
struct DivInstrumentES5506 { struct DivInstrumentES5506 {

View file

@ -457,15 +457,27 @@ int DivPlatformC64::dispatch(DivCommand c) {
switch (c.value>>4) { switch (c.value>>4) {
case 0: case 0:
chan[c.chan].attack=c.value&15; chan[c.chan].attack=c.value&15;
if (!no1EUpdate) {
rWrite(c.chan*7+5,(chan[c.chan].attack<<4)|(chan[c.chan].decay));
}
break; break;
case 1: case 1:
chan[c.chan].decay=c.value&15; chan[c.chan].decay=c.value&15;
if (!no1EUpdate) {
rWrite(c.chan*7+5,(chan[c.chan].attack<<4)|(chan[c.chan].decay));
}
break; break;
case 2: case 2:
chan[c.chan].sustain=c.value&15; chan[c.chan].sustain=c.value&15;
if (!no1EUpdate) {
rWrite(c.chan*7+6,(chan[c.chan].sustain<<4)|(chan[c.chan].release));
}
break; break;
case 3: case 3:
chan[c.chan].release=c.value&15; chan[c.chan].release=c.value&15;
if (!no1EUpdate) {
rWrite(c.chan*7+6,(chan[c.chan].sustain<<4)|(chan[c.chan].release));
}
break; break;
case 4: case 4:
chan[c.chan].ring=c.value; chan[c.chan].ring=c.value;
@ -481,6 +493,16 @@ int DivPlatformC64::dispatch(DivCommand c) {
break; break;
} }
break; break;
case DIV_CMD_C64_AD:
chan[c.chan].attack=c.value>>4;
chan[c.chan].decay=c.value&15;
rWrite(c.chan*7+5,(chan[c.chan].attack<<4)|(chan[c.chan].decay));
break;
case DIV_CMD_C64_SR:
chan[c.chan].sustain=c.value>>4;
chan[c.chan].release=c.value&15;
rWrite(c.chan*7+6,(chan[c.chan].sustain<<4)|(chan[c.chan].release));
break;
case DIV_CMD_MACRO_OFF: case DIV_CMD_MACRO_OFF:
chan[c.chan].std.mask(c.value,true); chan[c.chan].std.mask(c.value,true);
break; break;
@ -652,6 +674,7 @@ void DivPlatformC64::setFlags(const DivConfig& flags) {
if (sidCore==1) sid_fp->setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0); if (sidCore==1) sid_fp->setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0);
} }
keyPriority=flags.getBool("keyPriority",true); keyPriority=flags.getBool("keyPriority",true);
no1EUpdate=flags.getBool("no1EUpdate",false);
testAD=((flags.getInt("testAttack",0)&15)<<4)|(flags.getInt("testDecay",0)&15); testAD=((flags.getInt("testAttack",0)&15)<<4)|(flags.getInt("testDecay",0)&15);
testSR=((flags.getInt("testSustain",0)&15)<<4)|(flags.getInt("testRelease",0)&15); testSR=((flags.getInt("testSustain",0)&15)<<4)|(flags.getInt("testRelease",0)&15);

View file

@ -72,7 +72,7 @@ class DivPlatformC64: public DivDispatch {
unsigned char sidCore; unsigned char sidCore;
int filtCut, resetTime; int filtCut, resetTime;
bool keyPriority, sidIs6581, needInitTables; bool keyPriority, sidIs6581, needInitTables, no1EUpdate;
unsigned char chanOrder[3]; unsigned char chanOrder[3];
unsigned char testAD, testSR; unsigned char testAD, testSR;

View file

@ -134,6 +134,79 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
} }
writeControlUpper(i); writeControlUpper(i);
} }
// 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_SU);
int hwSeqCount=0;
while (chan[i].hwSeqPos<ins->su.hwSeqLen && hwSeqCount<8) {
bool leave=false;
unsigned char bound=ins->su.hwSeq[chan[i].hwSeqPos].bound;
unsigned char val=ins->su.hwSeq[chan[i].hwSeqPos].val;
unsigned short speed=ins->su.hwSeq[chan[i].hwSeqPos].speed;
switch (ins->su.hwSeq[chan[i].hwSeqPos].cmd) {
case DivInstrumentSoundUnit::DIV_SU_HWCMD_VOL:
chan[i].volSweepP=speed;
chan[i].volSweepV=val;
chan[i].volSweepB=bound;
chan[i].volSweep=(val>0);
chWrite(i,0x14,chan[i].volSweepP&0xff);
chWrite(i,0x15,chan[i].volSweepP>>8);
chWrite(i,0x16,chan[i].volSweepV);
chWrite(i,0x17,chan[i].volSweepB);
writeControlUpper(i);
break;
case DivInstrumentSoundUnit::DIV_SU_HWCMD_PITCH:
chan[i].freqSweepP=speed;
chan[i].freqSweepV=val;
chan[i].freqSweepB=bound;
chan[i].freqSweep=(val>0);
chWrite(i,0x10,chan[i].freqSweepP&0xff);
chWrite(i,0x11,chan[i].freqSweepP>>8);
chWrite(i,0x12,chan[i].freqSweepV);
chWrite(i,0x13,chan[i].freqSweepB);
writeControlUpper(i);
break;
case DivInstrumentSoundUnit::DIV_SU_HWCMD_CUT:
chan[i].cutSweepP=speed;
chan[i].cutSweepV=val;
chan[i].cutSweepB=bound;
chan[i].cutSweep=(val>0);
chWrite(i,0x18,chan[i].cutSweepP&0xff);
chWrite(i,0x19,chan[i].cutSweepP>>8);
chWrite(i,0x1a,chan[i].cutSweepV);
chWrite(i,0x1b,chan[i].cutSweepB);
writeControlUpper(i);
break;
case DivInstrumentSoundUnit::DIV_SU_HWCMD_WAIT:
chan[i].hwSeqDelay=(val+1)*parent->tickMult;
leave=true;
break;
case DivInstrumentSoundUnit::DIV_SU_HWCMD_WAIT_REL:
if (!chan[i].released) {
chan[i].hwSeqPos--;
leave=true;
}
break;
case DivInstrumentSoundUnit::DIV_SU_HWCMD_LOOP:
chan[i].hwSeqPos=val-1;
break;
case DivInstrumentSoundUnit::DIV_SU_HWCMD_LOOP_REL:
if (!chan[i].released) {
chan[i].hwSeqPos=val-1;
}
break;
}
chan[i].hwSeqPos++;
if (leave) break;
hwSeqCount++;
}
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,chan[i].switchRoles,2,chan[i].pitch2,chipClock,chan[i].switchRoles?CHIP_DIVIDER:CHIP_FREQBASE); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,chan[i].switchRoles,2,chan[i].pitch2,chipClock,chan[i].switchRoles?CHIP_DIVIDER:CHIP_FREQBASE);
@ -160,8 +233,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
} }
if (chan[i].keyOn) { if (chan[i].keyOn) {
if (chan[i].pcm) { if (chan[i].pcm) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); int sNum=chan[i].sample;
int sNum=ins->amiga.getSample(chan[i].note);
DivSample* sample=parent->getSample(sNum); DivSample* sample=parent->getSample(sNum);
if (sample!=NULL && sNum>=0 && sNum<parent->song.sampleLen) { if (sample!=NULL && sNum>=0 && sNum<parent->song.sampleLen) {
unsigned int sampleEnd=sampleOffSU[sNum]+(sample->getLoopEndPosition()); unsigned int sampleEnd=sampleOffSU[sNum]+(sample->getLoopEndPosition());
@ -220,6 +292,9 @@ int DivPlatformSoundUnit::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].released=false;
chan[c.chan].hwSeqPos=0;
chan[c.chan].hwSeqDelay=0;
chWrite(c.chan,0x02,chan[c.chan].vol); chWrite(c.chan,0x02,chan[c.chan].vol);
chan[c.chan].macroInit(ins); chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
@ -231,11 +306,14 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
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) {

View file

@ -30,12 +30,14 @@ class DivPlatformSoundUnit: public DivDispatch {
signed char pan; signed char pan;
unsigned char duty; unsigned char duty;
bool noise, pcm, phaseReset, filterPhaseReset, switchRoles; bool noise, pcm, phaseReset, filterPhaseReset, switchRoles;
bool pcmLoop, timerSync, freqSweep, volSweep, cutSweep; bool pcmLoop, timerSync, freqSweep, volSweep, cutSweep, released;
unsigned short freqSweepP, volSweepP, cutSweepP; unsigned short freqSweepP, volSweepP, cutSweepP;
unsigned char freqSweepB, volSweepB, cutSweepB; unsigned char freqSweepB, volSweepB, cutSweepB;
unsigned char freqSweepV, volSweepV, cutSweepV; unsigned char freqSweepV, volSweepV, cutSweepV;
unsigned short syncTimer; unsigned short syncTimer;
signed short wave; signed short wave;
unsigned short hwSeqPos;
short hwSeqDelay;
Channel(): Channel():
SharedChannel<signed char>(127), SharedChannel<signed char>(127),
cutoff(16383), cutoff(16383),
@ -56,6 +58,7 @@ class DivPlatformSoundUnit: public DivDispatch {
freqSweep(false), freqSweep(false),
volSweep(false), volSweep(false),
cutSweep(false), cutSweep(false),
released(false),
freqSweepP(0), freqSweepP(0),
volSweepP(0), volSweepP(0),
cutSweepP(0), cutSweepP(0),
@ -66,7 +69,9 @@ class DivPlatformSoundUnit: public DivDispatch {
volSweepV(0), volSweepV(0),
cutSweepV(0), cutSweepV(0),
syncTimer(0), syncTimer(0),
wave(0) {} wave(0),
hwSeqPos(0),
hwSeqDelay(0) {}
}; };
Channel chan[8]; Channel chan[8];
DivDispatchOscBuffer* oscBuf[8]; DivDispatchOscBuffer* oscBuf[8];

View file

@ -239,12 +239,16 @@ const char* cmdName[]={
"EXTERNAL", "EXTERNAL",
"C64_AD",
"C64_SR",
"ESFM_OP_PANNING",
"ESFM_OUTLVL",
"ESFM_MODIN",
"ESFM_ENV_DELAY",
"ALWAYS_SET_VOLUME", "ALWAYS_SET_VOLUME",
"DIV_CMD_ESFM_OP_PANNING",
"DIV_CMD_ESFM_OUTLVL",
"DIV_CMD_ESFM_MODIN",
"DIV_CMD_ESFM_ENV_DELAY"
}; };
static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!"); static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!");

View file

@ -606,6 +606,8 @@ void DivEngine::registerSystems() {
{0x1b, {DIV_CMD_C64_FILTER_RESET, "1Bxy: Reset cutoff (x: on new note; y: now)"}}, {0x1b, {DIV_CMD_C64_FILTER_RESET, "1Bxy: Reset cutoff (x: on new note; y: now)"}},
{0x1c, {DIV_CMD_C64_DUTY_RESET, "1Cxy: Reset pulse width (x: on new note; y: now)"}}, {0x1c, {DIV_CMD_C64_DUTY_RESET, "1Cxy: Reset pulse width (x: on new note; y: now)"}},
{0x1e, {DIV_CMD_C64_EXTENDED, "1Exy: Change additional parameters"}}, {0x1e, {DIV_CMD_C64_EXTENDED, "1Exy: Change additional parameters"}},
{0x20, {DIV_CMD_C64_AD, "20xy: Set attack/decay (x: attack; y: decay)"}},
{0x21, {DIV_CMD_C64_SR, "21xy: Set sustain/release (x: sustain; y: release)"}},
}; };
const EffectHandler c64FineDutyHandler(DIV_CMD_C64_FINE_DUTY, "3xxx: Set pulse width (0 to FFF)", effectValLong<12>); const EffectHandler c64FineDutyHandler(DIV_CMD_C64_FINE_DUTY, "3xxx: Set pulse width (0 to FFF)", effectValLong<12>);
const EffectHandler c64FineCutoffHandler(DIV_CMD_C64_FINE_CUTOFF, "4xxx: Set cutoff (0 to 7FF)", effectValLong<11>); const EffectHandler c64FineCutoffHandler(DIV_CMD_C64_FINE_CUTOFF, "4xxx: Set cutoff (0 to 7FF)", effectValLong<11>);

View file

@ -378,6 +378,16 @@ const char* gbHWSeqCmdTypes[6]={
"Loop until Release" "Loop until Release"
}; };
const char* suHWSeqCmdTypes[7]={
"Volume Sweep",
"Frequency Sweep",
"Cutoff Sweep",
"Wait",
"Wait for Release",
"Loop",
"Loop until Release"
};
const char* snesGainModes[5]={ const char* snesGainModes[5]={
"Direct", "Direct",
"Decrease (linear)", "Decrease (linear)",
@ -2453,7 +2463,6 @@ void FurnaceGUI::alterSampleMap(int column, int val) {
void FurnaceGUI::insTabSample(DivInstrument* ins) { void FurnaceGUI::insTabSample(DivInstrument* ins) {
const char* sampleTabName="Sample"; const char* sampleTabName="Sample";
if (ins->type==DIV_INS_SU) sampleTabName="Sound Unit";
if (ins->type==DIV_INS_NES) sampleTabName="DPCM"; if (ins->type==DIV_INS_NES) sampleTabName="DPCM";
if (ImGui::BeginTabItem(sampleTabName)) { if (ImGui::BeginTabItem(sampleTabName)) {
if (ins->type==DIV_INS_NES && e->song.oldDPCM) { if (ins->type==DIV_INS_NES && e->song.oldDPCM) {
@ -2482,9 +2491,6 @@ void FurnaceGUI::insTabSample(DivInstrument* ins) {
ins->type==DIV_INS_VRC6 || ins->type==DIV_INS_VRC6 ||
ins->type==DIV_INS_SU) { ins->type==DIV_INS_SU) {
P(ImGui::Checkbox("Use sample",&ins->amiga.useSample)); P(ImGui::Checkbox("Use sample",&ins->amiga.useSample));
if (ins->type==DIV_INS_SU) {
P(ImGui::Checkbox("Switch roles of frequency and phase reset timer",&ins->su.switchRoles));
}
if (ins->type==DIV_INS_X1_010) { if (ins->type==DIV_INS_X1_010) {
if (ImGui::InputInt("Sample bank slot##BANKSLOT",&ins->x1_010.bankSlot,1,4)) { PARAMETER if (ImGui::InputInt("Sample bank slot##BANKSLOT",&ins->x1_010.bankSlot,1,4)) { PARAMETER
if (ins->x1_010.bankSlot<0) ins->x1_010.bankSlot=0; if (ins->x1_010.bankSlot<0) ins->x1_010.bankSlot=0;
@ -5733,6 +5739,242 @@ void FurnaceGUI::drawInsEdit() {
P(ImGui::Checkbox("Don't test before new note",&ins->c64.noTest)); P(ImGui::Checkbox("Don't test before new note",&ins->c64.noTest));
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
if (ins->type==DIV_INS_SU) if (ImGui::BeginTabItem("Sound Unit")) {
P(ImGui::Checkbox("Switch roles of frequency and phase reset timer",&ins->su.switchRoles));
if (ImGui::BeginChild("HWSeqSU",ImGui::GetContentRegionAvail(),true,ImGuiWindowFlags_MenuBar)) {
ImGui::BeginMenuBar();
ImGui::Text("Hardware Sequence");
ImGui::EndMenuBar();
if (ins->su.hwSeqLen>0) if (ImGui::BeginTable("HWSeqListSU",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->su.hwSeqLen; i++) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%d (#%d)",curFrame,i);
ImGui::TableNextColumn();
ImGui::PushID(i);
if (ins->su.hwSeq[i].cmd>=DivInstrumentSoundUnit::DIV_SU_HWCMD_MAX) {
ins->su.hwSeq[i].cmd=0;
}
int cmd=ins->su.hwSeq[i].cmd;
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::Combo("##HWSeqCmd",&cmd,suHWSeqCmdTypes,DivInstrumentSoundUnit::DIV_SU_HWCMD_MAX)) {
if (ins->su.hwSeq[i].cmd!=cmd) {
ins->su.hwSeq[i].cmd=cmd;
ins->su.hwSeq[i].val=0;
ins->su.hwSeq[i].bound=0;
ins->su.hwSeq[i].speed=0;
}
}
bool somethingChanged=false;
switch (ins->su.hwSeq[i].cmd) {
case DivInstrumentSoundUnit::DIV_SU_HWCMD_VOL: {
int swPeriod=ins->su.hwSeq[i].speed;
int swBound=ins->su.hwSeq[i].bound;
int swVal=ins->su.hwSeq[i].val&31;
bool swDir=ins->su.hwSeq[i].val&32;
bool swLoop=ins->su.hwSeq[i].val&64;
bool swInvert=ins->su.hwSeq[i].val&128;
if (ImGui::InputInt("Period",&swPeriod,1,16)) {
if (swPeriod<0) swPeriod=0;
if (swPeriod>65535) swPeriod=65535;
somethingChanged=true;
}
if (CWSliderInt("Amount",&swVal,0,31)) {
somethingChanged=true;
}
if (CWSliderInt("Bound",&swBound,0,255)) {
somethingChanged=true;
}
if (ImGui::RadioButton("Up",swDir)) { PARAMETER
swDir=true;
somethingChanged=true;
}
ImGui::SameLine();
if (ImGui::RadioButton("Down",!swDir)) { PARAMETER
swDir=false;
somethingChanged=true;
}
if (ImGui::Checkbox("Loop",&swLoop)) { PARAMETER
somethingChanged=true;
}
ImGui::SameLine();
if (ImGui::Checkbox("Flip",&swInvert)) { PARAMETER
somethingChanged=true;
}
if (somethingChanged) {
ins->su.hwSeq[i].speed=swPeriod;
ins->su.hwSeq[i].bound=swBound;
ins->su.hwSeq[i].val=(swVal&31)|(swDir?32:0)|(swLoop?64:0)|(swInvert?128:0);
PARAMETER;
}
break;
}
case DivInstrumentSoundUnit::DIV_SU_HWCMD_PITCH:
case DivInstrumentSoundUnit::DIV_SU_HWCMD_CUT: {
int swPeriod=ins->su.hwSeq[i].speed;
int swBound=ins->su.hwSeq[i].bound;
int swVal=ins->su.hwSeq[i].val&127;
bool swDir=ins->su.hwSeq[i].val&128;
if (ImGui::InputInt("Period",&swPeriod,1,16)) {
if (swPeriod<0) swPeriod=0;
if (swPeriod>65535) swPeriod=65535;
somethingChanged=true;
}
if (CWSliderInt("Amount",&swVal,0,31)) {
somethingChanged=true;
}
if (CWSliderInt("Bound",&swBound,0,255)) {
somethingChanged=true;
}
if (ImGui::RadioButton("Up",swDir)) { PARAMETER
swDir=true;
somethingChanged=true;
}
ImGui::SameLine();
if (ImGui::RadioButton("Down",!swDir)) { PARAMETER
swDir=false;
somethingChanged=true;
}
if (somethingChanged) {
ins->su.hwSeq[i].speed=swPeriod;
ins->su.hwSeq[i].bound=swBound;
ins->su.hwSeq[i].val=(swVal&127)|(swDir?128:0);
PARAMETER;
}
break;
}
case DivInstrumentSoundUnit::DIV_SU_HWCMD_WAIT: {
int len=ins->su.hwSeq[i].val+1;
curFrame+=ins->su.hwSeq[i].val+1;
if (ImGui::InputInt("Ticks",&len)) {
if (len<1) len=1;
if (len>255) len=256;
somethingChanged=true;
}
if (somethingChanged) {
ins->su.hwSeq[i].val=len-1;
PARAMETER;
}
break;
}
case DivInstrumentSoundUnit::DIV_SU_HWCMD_WAIT_REL:
curFrame++;
break;
case DivInstrumentSoundUnit::DIV_SU_HWCMD_LOOP:
case DivInstrumentSoundUnit::DIV_SU_HWCMD_LOOP_REL: {
int pos=ins->su.hwSeq[i].val;
if (ImGui::InputInt("Position",&pos)) {
if (pos<0) pos=0;
if (pos>(ins->su.hwSeqLen-1)) pos=(ins->su.hwSeqLen-1);
somethingChanged=true;
}
if (somethingChanged) {
ins->su.hwSeq[i].val=pos;
PARAMETER;
}
break;
}
default:
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->su.hwSeq[i-1].cmd^=ins->su.hwSeq[i].cmd;
ins->su.hwSeq[i].cmd^=ins->su.hwSeq[i-1].cmd;
ins->su.hwSeq[i-1].cmd^=ins->su.hwSeq[i].cmd;
ins->su.hwSeq[i-1].speed^=ins->su.hwSeq[i].speed;
ins->su.hwSeq[i].speed^=ins->su.hwSeq[i-1].speed;
ins->su.hwSeq[i-1].speed^=ins->su.hwSeq[i].speed;
ins->su.hwSeq[i-1].val^=ins->su.hwSeq[i].val;
ins->su.hwSeq[i].val^=ins->su.hwSeq[i-1].val;
ins->su.hwSeq[i-1].val^=ins->su.hwSeq[i].val;
ins->su.hwSeq[i-1].bound^=ins->su.hwSeq[i].bound;
ins->su.hwSeq[i].bound^=ins->su.hwSeq[i-1].bound;
ins->su.hwSeq[i-1].bound^=ins->su.hwSeq[i].bound;
});
}
MARK_MODIFIED;
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_CHEVRON_DOWN "##HWCmdDown")) {
if (i<ins->su.hwSeqLen-1) {
e->lockEngine([ins,i]() {
ins->su.hwSeq[i+1].cmd^=ins->su.hwSeq[i].cmd;
ins->su.hwSeq[i].cmd^=ins->su.hwSeq[i+1].cmd;
ins->su.hwSeq[i+1].cmd^=ins->su.hwSeq[i].cmd;
ins->su.hwSeq[i+1].speed^=ins->su.hwSeq[i].speed;
ins->su.hwSeq[i].speed^=ins->su.hwSeq[i+1].speed;
ins->su.hwSeq[i+1].speed^=ins->su.hwSeq[i].speed;
ins->su.hwSeq[i+1].val^=ins->su.hwSeq[i].val;
ins->su.hwSeq[i].val^=ins->su.hwSeq[i+1].val;
ins->su.hwSeq[i+1].val^=ins->su.hwSeq[i].val;
ins->su.hwSeq[i+1].bound^=ins->su.hwSeq[i].bound;
ins->su.hwSeq[i].bound^=ins->su.hwSeq[i+1].bound;
ins->su.hwSeq[i+1].bound^=ins->su.hwSeq[i].bound;
});
}
MARK_MODIFIED;
}
ImGui::SameLine();
pushDestColor();
if (ImGui::Button(ICON_FA_TIMES "##HWCmdDel")) {
for (int j=i; j<ins->su.hwSeqLen-1; j++) {
ins->su.hwSeq[j].cmd=ins->su.hwSeq[j+1].cmd;
ins->su.hwSeq[j].speed=ins->su.hwSeq[j+1].speed;
ins->su.hwSeq[j].val=ins->su.hwSeq[j+1].val;
ins->su.hwSeq[j].bound=ins->su.hwSeq[j+1].bound;
}
ins->su.hwSeqLen--;
}
popDestColor();
ImGui::PopID();
}
ImGui::EndTable();
}
if (ImGui::Button(ICON_FA_PLUS "##HWCmdAdd")) {
if (ins->su.hwSeqLen<255) {
ins->su.hwSeq[ins->su.hwSeqLen].cmd=0;
ins->su.hwSeq[ins->su.hwSeqLen].speed=0;
ins->su.hwSeq[ins->su.hwSeqLen].val=0;
ins->su.hwSeq[ins->su.hwSeqLen].bound=0;
ins->su.hwSeqLen++;
}
}
}
ImGui::EndChild();
ImGui::EndTabItem();
}
if (ins->type==DIV_INS_MSM6258 || if (ins->type==DIV_INS_MSM6258 ||
ins->type==DIV_INS_MSM6295 || ins->type==DIV_INS_MSM6295 ||
ins->type==DIV_INS_ADPCMA || ins->type==DIV_INS_ADPCMA ||

View file

@ -976,7 +976,7 @@ void FurnaceGUI::initSystemPresets() {
); );
ENTRY( ENTRY(
"PC + AdLib (drums mode)", { "PC + AdLib (drums mode)", {
CH(DIV_SYSTEM_OPL2, 1.0f, 0, ""), CH(DIV_SYSTEM_OPL2_DRUMS, 1.0f, 0, ""),
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "") CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
} }
); );

View file

@ -586,6 +586,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
case DIV_SYSTEM_C64_6581: { case DIV_SYSTEM_C64_6581: {
int clockSel=flags.getInt("clockSel",0); int clockSel=flags.getInt("clockSel",0);
bool keyPriority=flags.getBool("keyPriority",true); bool keyPriority=flags.getBool("keyPriority",true);
bool no1EUpdate=flags.getBool("no1EUpdate",false);
int testAttack=flags.getInt("testAttack",0); int testAttack=flags.getInt("testAttack",0);
int testDecay=flags.getInt("testDecay",0); int testDecay=flags.getInt("testDecay",0);
int testSustain=flags.getInt("testSustain",0); int testSustain=flags.getInt("testSustain",0);
@ -644,10 +645,15 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
altered=true; altered=true;
} }
if (ImGui::Checkbox("Disable 1Exy env update (compatibility)",&no1EUpdate)) {
altered=true;
}
if (altered) { if (altered) {
e->lockSave([&]() { e->lockSave([&]() {
flags.set("clockSel",clockSel); flags.set("clockSel",clockSel);
flags.set("keyPriority",keyPriority); flags.set("keyPriority",keyPriority);
flags.set("no1EUpdate",no1EUpdate);
flags.set("testAttack",testAttack); flags.set("testAttack",testAttack);
flags.set("testDecay",testDecay); flags.set("testDecay",testDecay);
flags.set("testSustain",testSustain); flags.set("testSustain",testSustain);