Merge branch 'master' of https://github.com/tildearrow/furnace into es5506_alt
* 'master' of https://github.com/tildearrow/furnace: GUI: sample/macro zoom with ctrl-wheel GUI: prevent division by zero GUI: possibly fix an instrument saving issue GUI: add chip randomizer button update to-do list oops NES: add DMC write effect SoundUnit: implement panning QSound: implement panning macro - PLEASE READ further improve channel allocation update format.md for eventual ExtCh extra systems improvements to swap/stomp channel # Conflicts: # src/gui/insEdit.cpp
This commit is contained in:
commit
72e8bb89a7
3
TODO.md
3
TODO.md
|
@ -6,8 +6,6 @@
|
||||||
|
|
||||||
# to-do for 0.6pre1
|
# to-do for 0.6pre1
|
||||||
|
|
||||||
- panning macro
|
|
||||||
- QSound?
|
|
||||||
- piano/input pad
|
- piano/input pad
|
||||||
- note input via piano
|
- note input via piano
|
||||||
- input pad
|
- input pad
|
||||||
|
@ -47,3 +45,4 @@
|
||||||
- Apply button in settings
|
- Apply button in settings
|
||||||
- better FM chip names (number and codename)
|
- better FM chip names (number and codename)
|
||||||
- find and replace
|
- find and replace
|
||||||
|
- precise panning effects (80xx linear, 81xx/82xx per-channel)
|
||||||
|
|
|
@ -2,10 +2,13 @@
|
||||||
|
|
||||||
the console from Nintendo that plays Super Mario Bros. and helped revive the agonizing video game market in the US during mid-80s.
|
the console from Nintendo that plays Super Mario Bros. and helped revive the agonizing video game market in the US during mid-80s.
|
||||||
|
|
||||||
also known as Famicom. It is a five-channel PSG: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel
|
also known as Famicom. It is a five-channel PSG: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel.
|
||||||
|
|
||||||
# effects
|
# effects
|
||||||
|
|
||||||
|
- `11xx`: write to delta modulation counter.
|
||||||
|
- this may be used to attenuate the triangle and noise channels.
|
||||||
|
- will not work if a sample is playing.
|
||||||
- `12xx`: set duty cycle or noise mode of channel.
|
- `12xx`: set duty cycle or noise mode of channel.
|
||||||
- may be 0-3 for the pulse channels and 0-1 for the noise channel.
|
- may be 0-3 for the pulse channels and 0-1 for the noise channel.
|
||||||
- `13xy`: setup sweep up.
|
- `13xy`: setup sweep up.
|
||||||
|
@ -15,4 +18,4 @@ also known as Famicom. It is a five-channel PSG: first two channels play pulse w
|
||||||
- `14xy`: setup sweep down.
|
- `14xy`: setup sweep down.
|
||||||
- `x` is the time.
|
- `x` is the time.
|
||||||
- `y` is the shift.
|
- `y` is the shift.
|
||||||
- set to 0 to disable it.
|
- set to 0 to disable it.
|
|
@ -12,7 +12,8 @@ There are also 3 ADPCM channels, however they cannot be used in Furnace yet. The
|
||||||
|
|
||||||
# effects
|
# effects
|
||||||
|
|
||||||
- `08xx`: Set panning. Valid range is 00-20. 00 for full left, 10 for center and 20 for full right. It is also possible to bypass the QSound algorithm by using the range 30-50.
|
- `10xx`: set echo feedback level.
|
||||||
- `10xx`: Set echo feedback level. This effect will apply to all channels.
|
- this effect will apply to all channels.
|
||||||
- `11xx`: Set echo level.
|
- `11xx`: set echo level.
|
||||||
- `3xxx`: Set the length of the echo delay buffer.
|
- `12xx`: toggle QSound algorithm (on by default).
|
||||||
|
- `3xxx`: set the length of the echo delay buffer.
|
||||||
|
|
|
@ -213,6 +213,8 @@ size | description
|
||||||
| - 0xb3: Yamaha Y8950 drums - 12 channels
|
| - 0xb3: Yamaha Y8950 drums - 12 channels
|
||||||
| - 0xb4: Konami SCC+ - 5 channels
|
| - 0xb4: Konami SCC+ - 5 channels
|
||||||
| - 0xb5: tildearrow Sound Unit - 8 channels
|
| - 0xb5: tildearrow Sound Unit - 8 channels
|
||||||
|
| - 0xb6: OPN extended - 9 channels
|
||||||
|
| - 0xb7: PC-98 extended - 19 channels
|
||||||
| - 0xde: YM2610B extended - 19 channels
|
| - 0xde: YM2610B extended - 19 channels
|
||||||
| - 0xe0: QSound - 19 channels
|
| - 0xe0: QSound - 19 channels
|
||||||
| - 0xfd: Dummy System - 8 channels
|
| - 0xfd: Dummy System - 8 channels
|
||||||
|
|
|
@ -86,6 +86,7 @@ enum DivDispatchCmds {
|
||||||
DIV_CMD_PCE_LFO_SPEED, // (speed)
|
DIV_CMD_PCE_LFO_SPEED, // (speed)
|
||||||
|
|
||||||
DIV_CMD_NES_SWEEP, // (direction, value)
|
DIV_CMD_NES_SWEEP, // (direction, value)
|
||||||
|
DIV_CMD_NES_DMC, // (value)
|
||||||
|
|
||||||
DIV_CMD_C64_CUTOFF, // (value)
|
DIV_CMD_C64_CUTOFF, // (value)
|
||||||
DIV_CMD_C64_RESONANCE, // (value)
|
DIV_CMD_C64_RESONANCE, // (value)
|
||||||
|
@ -125,6 +126,7 @@ enum DivDispatchCmds {
|
||||||
DIV_CMD_QSOUND_ECHO_FEEDBACK,
|
DIV_CMD_QSOUND_ECHO_FEEDBACK,
|
||||||
DIV_CMD_QSOUND_ECHO_DELAY,
|
DIV_CMD_QSOUND_ECHO_DELAY,
|
||||||
DIV_CMD_QSOUND_ECHO_LEVEL,
|
DIV_CMD_QSOUND_ECHO_LEVEL,
|
||||||
|
DIV_CMD_QSOUND_SURROUND,
|
||||||
|
|
||||||
DIV_CMD_X1_010_ENVELOPE_SHAPE,
|
DIV_CMD_X1_010_ENVELOPE_SHAPE,
|
||||||
DIV_CMD_X1_010_ENVELOPE_ENABLE,
|
DIV_CMD_X1_010_ENVELOPE_ENABLE,
|
||||||
|
|
|
@ -695,6 +695,20 @@ void DivEngine::swapChannels(int src, int dest) {
|
||||||
song.pat[src].effectCols^=song.pat[dest].effectCols;
|
song.pat[src].effectCols^=song.pat[dest].effectCols;
|
||||||
song.pat[dest].effectCols^=song.pat[src].effectCols;
|
song.pat[dest].effectCols^=song.pat[src].effectCols;
|
||||||
song.pat[src].effectCols^=song.pat[dest].effectCols;
|
song.pat[src].effectCols^=song.pat[dest].effectCols;
|
||||||
|
|
||||||
|
String prevChanName=song.chanName[src];
|
||||||
|
String prevChanShortName=song.chanShortName[src];
|
||||||
|
bool prevChanShow=song.chanShow[src];
|
||||||
|
bool prevChanCollapse=song.chanCollapse[src];
|
||||||
|
|
||||||
|
song.chanName[src]=song.chanName[dest];
|
||||||
|
song.chanShortName[src]=song.chanShortName[dest];
|
||||||
|
song.chanShow[src]=song.chanShow[dest];
|
||||||
|
song.chanCollapse[src]=song.chanCollapse[dest];
|
||||||
|
song.chanName[dest]=prevChanName;
|
||||||
|
song.chanShortName[dest]=prevChanShortName;
|
||||||
|
song.chanShow[dest]=prevChanShow;
|
||||||
|
song.chanCollapse[dest]=prevChanCollapse;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivEngine::stompChannel(int ch) {
|
void DivEngine::stompChannel(int ch) {
|
||||||
|
@ -704,6 +718,10 @@ void DivEngine::stompChannel(int ch) {
|
||||||
}
|
}
|
||||||
song.pat[ch].wipePatterns();
|
song.pat[ch].wipePatterns();
|
||||||
song.pat[ch].effectCols=1;
|
song.pat[ch].effectCols=1;
|
||||||
|
song.chanName[ch]="";
|
||||||
|
song.chanShortName[ch]="";
|
||||||
|
song.chanShow[ch]=true;
|
||||||
|
song.chanCollapse[ch]=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DivEngine::swapChannelsP(int src, int dest) {
|
void DivEngine::swapChannelsP(int src, int dest) {
|
||||||
|
@ -1054,6 +1072,28 @@ int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2
|
||||||
base+((pitch*octave)>>1)+pitch2;
|
base+((pitch*octave)>>1)+pitch2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int DivEngine::convertPanSplitToLinear(unsigned int val, unsigned char bits, int range) {
|
||||||
|
int panL=val>>bits;
|
||||||
|
int panR=val&((1<<bits)-1);
|
||||||
|
int diff=panR-panL;
|
||||||
|
float pan=0.5f;
|
||||||
|
if (diff!=0) {
|
||||||
|
pan=(1.0f+((float)diff/(float)MAX(panL,panR)))*0.5f;
|
||||||
|
}
|
||||||
|
return pan*range;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int DivEngine::convertPanLinearToSplit(int val, unsigned char bits, int range) {
|
||||||
|
if (val<0) val=0;
|
||||||
|
if (val>range) val=range;
|
||||||
|
int maxV=(1<<bits)-1;
|
||||||
|
int panL=(((range-val)*maxV*2))/range;
|
||||||
|
int panR=((val)*maxV*2)/range;
|
||||||
|
if (panL>maxV) panL=maxV;
|
||||||
|
if (panR>maxV) panR=maxV;
|
||||||
|
return (panL<<bits)|panR;
|
||||||
|
}
|
||||||
|
|
||||||
void DivEngine::play() {
|
void DivEngine::play() {
|
||||||
BUSY_BEGIN_SOFT;
|
BUSY_BEGIN_SOFT;
|
||||||
sPreview.sample=-1;
|
sPreview.sample=-1;
|
||||||
|
@ -1497,6 +1537,9 @@ int DivEngine::addInstrument(int refChan) {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (sysOfChan[refChan]==DIV_SYSTEM_QSOUND) {
|
||||||
|
*ins=song.nullInsQSound;
|
||||||
|
}
|
||||||
ins->name=fmt::sprintf("Instrument %d",insCount);
|
ins->name=fmt::sprintf("Instrument %d",insCount);
|
||||||
ins->type=prefType;
|
ins->type=prefType;
|
||||||
saveLock.lock();
|
saveLock.lock();
|
||||||
|
@ -2184,6 +2227,7 @@ void DivEngine::noteOff(int chan) {
|
||||||
void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
|
void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
|
||||||
bool isViable[DIV_MAX_CHANS];
|
bool isViable[DIV_MAX_CHANS];
|
||||||
bool canPlayAnyway=false;
|
bool canPlayAnyway=false;
|
||||||
|
bool notInViableChannel=false;
|
||||||
if (midiBaseChan<0) midiBaseChan=0;
|
if (midiBaseChan<0) midiBaseChan=0;
|
||||||
if (midiBaseChan>=chans) midiBaseChan=chans-1;
|
if (midiBaseChan>=chans) midiBaseChan=chans-1;
|
||||||
int finalChan=midiBaseChan;
|
int finalChan=midiBaseChan;
|
||||||
|
@ -2197,6 +2241,7 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
|
||||||
|
|
||||||
// 1. check which channels are viable for this instrument
|
// 1. check which channels are viable for this instrument
|
||||||
DivInstrument* insInst=getIns(ins);
|
DivInstrument* insInst=getIns(ins);
|
||||||
|
if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type) notInViableChannel=true;
|
||||||
for (int i=0; i<chans; i++) {
|
for (int i=0; i<chans; i++) {
|
||||||
if (ins==-1 || ins>=song.insLen || getPreferInsType(i)==insInst->type || getPreferInsSecondType(i)==insInst->type) {
|
if (ins==-1 || ins>=song.insLen || getPreferInsType(i)==insInst->type || getPreferInsSecondType(i)==insInst->type) {
|
||||||
if (insInst->type==DIV_INS_OPL) {
|
if (insInst->type==DIV_INS_OPL) {
|
||||||
|
@ -2219,7 +2264,7 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
|
||||||
|
|
||||||
// 2. find a free channel
|
// 2. find a free channel
|
||||||
do {
|
do {
|
||||||
if (isViable[finalChan] && chan[finalChan].midiNote==-1 && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType)) {
|
if (isViable[finalChan] && chan[finalChan].midiNote==-1 && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel)) {
|
||||||
chan[finalChan].midiNote=note;
|
chan[finalChan].midiNote=note;
|
||||||
chan[finalChan].midiAge=midiAgeCounter++;
|
chan[finalChan].midiAge=midiAgeCounter++;
|
||||||
pendingNotes.push(DivNoteEvent(finalChan,ins,note,vol,true));
|
pendingNotes.push(DivNoteEvent(finalChan,ins,note,vol,true));
|
||||||
|
@ -2233,7 +2278,7 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) {
|
||||||
// 3. find the oldest channel
|
// 3. find the oldest channel
|
||||||
int candidate=finalChan;
|
int candidate=finalChan;
|
||||||
do {
|
do {
|
||||||
if (isViable[finalChan] && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType) && chan[finalChan].midiAge<chan[candidate].midiAge) {
|
if (isViable[finalChan] && (insInst->type==DIV_INS_OPL || getChannelType(finalChan)==finalChanType || notInViableChannel) && chan[finalChan].midiAge<chan[candidate].midiAge) {
|
||||||
candidate=finalChan;
|
candidate=finalChan;
|
||||||
}
|
}
|
||||||
if (++finalChan>=chans) {
|
if (++finalChan>=chans) {
|
||||||
|
|
|
@ -477,6 +477,10 @@ class DivEngine {
|
||||||
// calculate frequency/period
|
// calculate frequency/period
|
||||||
int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0);
|
int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0);
|
||||||
|
|
||||||
|
// convert panning formats
|
||||||
|
int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range);
|
||||||
|
unsigned int convertPanLinearToSplit(int val, unsigned char bits, int range);
|
||||||
|
|
||||||
// find song loop position
|
// find song loop position
|
||||||
void walkSong(int& loopOrder, int& loopRow, int& loopEnd);
|
void walkSong(int& loopOrder, int& loopRow, int& loopEnd);
|
||||||
|
|
||||||
|
|
|
@ -90,9 +90,9 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
||||||
} envChanged;
|
} envChanged;
|
||||||
|
|
||||||
signed int k1Offs, k2Offs;
|
signed int k1Offs, k2Offs;
|
||||||
int vol, lVol, rVol;
|
unsigned int vol, lVol, rVol;
|
||||||
int outVol, outLVol, outRVol;
|
unsigned int outVol, outLVol, outRVol;
|
||||||
int resLVol, resRVol;
|
unsigned int resLVol, resRVol;
|
||||||
DivInstrumentES5506::Filter filter;
|
DivInstrumentES5506::Filter filter;
|
||||||
DivInstrumentES5506::Envelope envelope;
|
DivInstrumentES5506::Envelope envelope;
|
||||||
DivMacroInt std;
|
DivMacroInt std;
|
||||||
|
|
|
@ -59,6 +59,9 @@ const char** DivPlatformNES::getRegisterSheet() {
|
||||||
|
|
||||||
const char* DivPlatformNES::getEffectName(unsigned char effect) {
|
const char* DivPlatformNES::getEffectName(unsigned char effect) {
|
||||||
switch (effect) {
|
switch (effect) {
|
||||||
|
case 0x11:
|
||||||
|
return "Write to delta modulation counter (0 to 7F)";
|
||||||
|
break;
|
||||||
case 0x12:
|
case 0x12:
|
||||||
return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)";
|
return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)";
|
||||||
break;
|
break;
|
||||||
|
@ -421,6 +424,9 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
||||||
}
|
}
|
||||||
rWrite(0x4001+(c.chan*4),chan[c.chan].sweep);
|
rWrite(0x4001+(c.chan*4),chan[c.chan].sweep);
|
||||||
break;
|
break;
|
||||||
|
case DIV_CMD_NES_DMC:
|
||||||
|
rWrite(0x4011,c.value&0x7f);
|
||||||
|
break;
|
||||||
case DIV_CMD_SAMPLE_BANK:
|
case DIV_CMD_SAMPLE_BANK:
|
||||||
sampleBank=c.value;
|
sampleBank=c.value;
|
||||||
if (sampleBank>(parent->song.sample.size()/12)) {
|
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||||
|
|
|
@ -257,6 +257,9 @@ const char* DivPlatformQSound::getEffectName(unsigned char effect) {
|
||||||
case 0x11:
|
case 0x11:
|
||||||
return "11xx: Set channel echo level (00 to FF)";
|
return "11xx: Set channel echo level (00 to FF)";
|
||||||
break;
|
break;
|
||||||
|
case 0x12:
|
||||||
|
return "12xx: Toggle QSound algorithm (0: disabled; 1: enabled)";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
if ((effect & 0xf0) == 0x30) {
|
if ((effect & 0xf0) == 0x30) {
|
||||||
return "3xxx: Set echo delay buffer length (000 to AA5)";
|
return "3xxx: Set echo delay buffer length (000 to AA5)";
|
||||||
|
@ -335,6 +338,15 @@ void DivPlatformQSound::tick(bool sysTick) {
|
||||||
}
|
}
|
||||||
chan[i].freqChanged=true;
|
chan[i].freqChanged=true;
|
||||||
}
|
}
|
||||||
|
if (chan[i].std.panL.had) { // panning
|
||||||
|
chan[i].panning=chan[i].std.panL.val+16;
|
||||||
|
}
|
||||||
|
if (chan[i].std.panR.had) { // surround
|
||||||
|
chan[i].surround=chan[i].std.panR.val;
|
||||||
|
}
|
||||||
|
if (chan[i].std.panL.had || chan[i].std.panR.had) {
|
||||||
|
immWrite(Q1_PAN+i,chan[i].panning+0x110+(chan[i].surround?0:0x30));
|
||||||
|
}
|
||||||
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_AMIGA);
|
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
|
||||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
|
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2);
|
||||||
|
@ -429,7 +441,8 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
||||||
return chan[c.chan].outVol;
|
return chan[c.chan].outVol;
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_PANNING:
|
case DIV_CMD_PANNING:
|
||||||
immWrite(Q1_PAN+c.chan, c.value + 0x110);
|
chan[c.chan].panning=parent->convertPanSplitToLinear(c.value,4,32);
|
||||||
|
immWrite(Q1_PAN+c.chan,chan[c.chan].panning+0x110+(chan[c.chan].surround?0:0x30));
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_QSOUND_ECHO_LEVEL:
|
case DIV_CMD_QSOUND_ECHO_LEVEL:
|
||||||
immWrite(Q1_ECHO+c.chan, c.value << 7);
|
immWrite(Q1_ECHO+c.chan, c.value << 7);
|
||||||
|
@ -440,6 +453,10 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
||||||
case DIV_CMD_QSOUND_ECHO_DELAY:
|
case DIV_CMD_QSOUND_ECHO_DELAY:
|
||||||
immWrite(Q1_ECHO_LENGTH, (c.value > 2725 ? 0xfff : 0xfff - (2725 - c.value)));
|
immWrite(Q1_ECHO_LENGTH, (c.value > 2725 ? 0xfff : 0xfff - (2725 - c.value)));
|
||||||
break;
|
break;
|
||||||
|
case DIV_CMD_QSOUND_SURROUND:
|
||||||
|
chan[c.chan].surround=c.value;
|
||||||
|
immWrite(Q1_PAN+c.chan,chan[c.chan].panning+0x110+(chan[c.chan].surround?0:0x30));
|
||||||
|
break;
|
||||||
case DIV_CMD_PITCH:
|
case DIV_CMD_PITCH:
|
||||||
chan[c.chan].pitch=c.value;
|
chan[c.chan].pitch=c.value;
|
||||||
chan[c.chan].freqChanged=true;
|
chan[c.chan].freqChanged=true;
|
||||||
|
|
|
@ -33,7 +33,7 @@ class DivPlatformQSound: public DivDispatch {
|
||||||
int sample, wave, ins;
|
int sample, wave, ins;
|
||||||
int note;
|
int note;
|
||||||
int panning;
|
int panning;
|
||||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave;
|
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, surround;
|
||||||
int vol, outVol;
|
int vol, outVol;
|
||||||
DivMacroInt std;
|
DivMacroInt std;
|
||||||
void macroInit(DivInstrument* which) {
|
void macroInit(DivInstrument* which) {
|
||||||
|
@ -57,6 +57,8 @@ class DivPlatformQSound: public DivDispatch {
|
||||||
keyOn(false),
|
keyOn(false),
|
||||||
keyOff(false),
|
keyOff(false),
|
||||||
inPorta(false),
|
inPorta(false),
|
||||||
|
useWave(false),
|
||||||
|
surround(true),
|
||||||
vol(255),
|
vol(255),
|
||||||
outVol(255) {}
|
outVol(255) {}
|
||||||
};
|
};
|
||||||
|
|
|
@ -270,7 +270,7 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DIV_CMD_PANNING: {
|
case DIV_CMD_PANNING: {
|
||||||
chan[c.chan].pan=c.value;
|
chan[c.chan].pan=parent->convertPanSplitToLinear(c.value,4,254)-127;
|
||||||
chWrite(c.chan,0x03,chan[c.chan].pan);
|
chWrite(c.chan,0x03,chan[c.chan].pan);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,7 @@ const char* cmdName[]={
|
||||||
"PCE_LFO_SPEED",
|
"PCE_LFO_SPEED",
|
||||||
|
|
||||||
"NES_SWEEP",
|
"NES_SWEEP",
|
||||||
|
"NES_DMC",
|
||||||
|
|
||||||
"C64_CUTOFF",
|
"C64_CUTOFF",
|
||||||
"C64_RESONANCE",
|
"C64_RESONANCE",
|
||||||
|
@ -127,6 +128,7 @@ const char* cmdName[]={
|
||||||
"QSOUND_ECHO_FEEDBACK",
|
"QSOUND_ECHO_FEEDBACK",
|
||||||
"QSOUND_ECHO_DELAY",
|
"QSOUND_ECHO_DELAY",
|
||||||
"QSOUND_ECHO_LEVEL",
|
"QSOUND_ECHO_LEVEL",
|
||||||
|
"QSOUND_SURROUND",
|
||||||
|
|
||||||
"X1_010_ENVELOPE_SHAPE",
|
"X1_010_ENVELOPE_SHAPE",
|
||||||
"X1_010_ENVELOPE_ENABLE",
|
"X1_010_ENVELOPE_ENABLE",
|
||||||
|
@ -325,6 +327,9 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
|
||||||
case DIV_SYSTEM_NES:
|
case DIV_SYSTEM_NES:
|
||||||
case DIV_SYSTEM_MMC5:
|
case DIV_SYSTEM_MMC5:
|
||||||
switch (effect) {
|
switch (effect) {
|
||||||
|
case 0x11: // DMC write
|
||||||
|
dispatchCmd(DivCommand(DIV_CMD_NES_DMC,ch,effectVal));
|
||||||
|
break;
|
||||||
case 0x12: // duty or noise mode
|
case 0x12: // duty or noise mode
|
||||||
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
|
dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal));
|
||||||
break;
|
break;
|
||||||
|
@ -443,6 +448,9 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
|
||||||
case 0x11: // echo level
|
case 0x11: // echo level
|
||||||
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_LEVEL,ch,effectVal));
|
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_LEVEL,ch,effectVal));
|
||||||
break;
|
break;
|
||||||
|
case 0x12: // surround
|
||||||
|
dispatchCmd(DivCommand(DIV_CMD_QSOUND_SURROUND,ch,effectVal));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
if ((effect&0xf0)==0x30) {
|
if ((effect&0xf0)==0x30) {
|
||||||
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_DELAY,ch,((effect & 0x0f) << 8) | effectVal));
|
dispatchCmd(DivCommand(DIV_CMD_QSOUND_ECHO_DELAY,ch,((effect & 0x0f) << 8) | effectVal));
|
||||||
|
|
|
@ -347,7 +347,7 @@ struct DivSong {
|
||||||
bool chanShow[DIV_MAX_CHANS];
|
bool chanShow[DIV_MAX_CHANS];
|
||||||
bool chanCollapse[DIV_MAX_CHANS];
|
bool chanCollapse[DIV_MAX_CHANS];
|
||||||
|
|
||||||
DivInstrument nullIns, nullInsOPLL, nullInsOPL;
|
DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsQSound;
|
||||||
DivWavetable nullWave;
|
DivWavetable nullWave;
|
||||||
DivSample nullSample;
|
DivSample nullSample;
|
||||||
|
|
||||||
|
@ -473,6 +473,8 @@ struct DivSong {
|
||||||
nullInsOPL.fm.op[1].rr=12;
|
nullInsOPL.fm.op[1].rr=12;
|
||||||
nullInsOPL.fm.op[1].mult=1;
|
nullInsOPL.fm.op[1].mult=1;
|
||||||
nullInsOPL.name="This is a bug! Report!";
|
nullInsOPL.name="This is a bug! Report!";
|
||||||
|
|
||||||
|
nullInsQSound.std.panLMacro.mode=true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -337,7 +337,7 @@ void putDispatchChan(void* data, int chanNum, int type) {
|
||||||
case DIV_SYSTEM_ES5506: {
|
case DIV_SYSTEM_ES5506: {
|
||||||
DivPlatformES5506::Channel* ch=(DivPlatformES5506::Channel*)data;
|
DivPlatformES5506::Channel* ch=(DivPlatformES5506::Channel*)data;
|
||||||
ImGui::Text("> ES5506");
|
ImGui::Text("> ES5506");
|
||||||
ImGui::Text("* freq: %.4x",ch->freq);
|
ImGui::Text("* freq: %.5x",ch->freq);
|
||||||
ImGui::Text(" - base: %d",ch->baseFreq);
|
ImGui::Text(" - base: %d",ch->baseFreq);
|
||||||
ImGui::Text(" - pitch: %d",ch->pitch);
|
ImGui::Text(" - pitch: %d",ch->pitch);
|
||||||
ImGui::Text(" - pitch2: %d",ch->pitch2);
|
ImGui::Text(" - pitch2: %d",ch->pitch2);
|
||||||
|
|
|
@ -2903,6 +2903,10 @@ bool FurnaceGUI::loop() {
|
||||||
if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) {
|
if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) {
|
||||||
bool openOpen=false;
|
bool openOpen=false;
|
||||||
//ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard;
|
//ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard;
|
||||||
|
if (curFileDialog==GUI_FILE_INS_OPEN && prevIns!=-3) {
|
||||||
|
curIns=prevIns;
|
||||||
|
prevIns=-3;
|
||||||
|
}
|
||||||
switch (curFileDialog) {
|
switch (curFileDialog) {
|
||||||
case GUI_FILE_OPEN:
|
case GUI_FILE_OPEN:
|
||||||
case GUI_FILE_SAVE:
|
case GUI_FILE_SAVE:
|
||||||
|
@ -2947,9 +2951,6 @@ bool FurnaceGUI::loop() {
|
||||||
workingDirLayout=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
workingDirLayout=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (prevIns!=-3) {
|
|
||||||
curIns=prevIns;
|
|
||||||
}
|
|
||||||
if (fileDialog->accepted()) {
|
if (fileDialog->accepted()) {
|
||||||
fileName=fileDialog->getFileName();
|
fileName=fileDialog->getFileName();
|
||||||
if (fileName!="") {
|
if (fileName!="") {
|
||||||
|
@ -3787,6 +3788,7 @@ FurnaceGUI::FurnaceGUI():
|
||||||
preserveChanPos(false),
|
preserveChanPos(false),
|
||||||
vgmExportVersion(0x171),
|
vgmExportVersion(0x171),
|
||||||
drawHalt(10),
|
drawHalt(10),
|
||||||
|
macroPointSize(16),
|
||||||
curFileDialog(GUI_FILE_OPEN),
|
curFileDialog(GUI_FILE_OPEN),
|
||||||
warnAction(GUI_WARN_OPEN),
|
warnAction(GUI_WARN_OPEN),
|
||||||
postWarnAction(GUI_WARN_GENERIC),
|
postWarnAction(GUI_WARN_GENERIC),
|
||||||
|
|
|
@ -739,6 +739,7 @@ class FurnaceGUI {
|
||||||
bool willExport[32];
|
bool willExport[32];
|
||||||
int vgmExportVersion;
|
int vgmExportVersion;
|
||||||
int drawHalt;
|
int drawHalt;
|
||||||
|
int macroPointSize;
|
||||||
|
|
||||||
FurnaceGUIFileDialogs curFileDialog;
|
FurnaceGUIFileDialogs curFileDialog;
|
||||||
FurnaceGUIWarnings warnAction;
|
FurnaceGUIWarnings warnAction;
|
||||||
|
|
|
@ -228,6 +228,11 @@ const char* macroRelativeMode[3]={
|
||||||
"Relative",
|
"Relative",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
const char* macroQSoundMode[3]={
|
||||||
|
"Independent",
|
||||||
|
"QSound",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
const char* macroDummyMode[1]={NULL};
|
const char* macroDummyMode[1]={NULL};
|
||||||
|
|
||||||
|
@ -1120,7 +1125,7 @@ void FurnaceGUI::drawGBEnv(unsigned char vol, unsigned char len, unsigned char s
|
||||||
} \
|
} \
|
||||||
if (macroMode && displayModeName[0]!=NULL) { \
|
if (macroMode && displayModeName[0]!=NULL) { \
|
||||||
for (unsigned int m=0; displayModeName[m]!=NULL; m++) { \
|
for (unsigned int m=0; displayModeName[m]!=NULL; m++) { \
|
||||||
if (ImGui::RadioButton(displayModeName[m],macro.mode==m)) { \
|
if (ImGui::RadioButton(fmt::sprintf("%s##IMacroMode_%s",displayModeName[m],macro.name).c_str(),macro.mode==m)) { \
|
||||||
macro.mode=m; \
|
macro.mode=m; \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
|
@ -1166,6 +1171,11 @@ void FurnaceGUI::drawGBEnv(unsigned char vol, unsigned char len, unsigned char s
|
||||||
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \
|
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \
|
||||||
} \
|
} \
|
||||||
if (displayLoop) { \
|
if (displayLoop) { \
|
||||||
|
if (ImGui::IsItemHovered() && ctrlWheeling) { \
|
||||||
|
macroPointSize+=wheelY; \
|
||||||
|
if (macroPointSize<1) macroPointSize=1; \
|
||||||
|
if (macroPointSize>256) macroPointSize=256; \
|
||||||
|
} \
|
||||||
if (drawSlider) { \
|
if (drawSlider) { \
|
||||||
ImGui::SameLine(); \
|
ImGui::SameLine(); \
|
||||||
CWVSliderInt("##IMacroPos_" macroName,ImVec2(20.0f*dpiScale,displayHeight*dpiScale),sliderVal,sliderLow,sliderHigh); \
|
CWVSliderInt("##IMacroPos_" macroName,ImVec2(20.0f*dpiScale,displayHeight*dpiScale),sliderVal,sliderLow,sliderHigh); \
|
||||||
|
@ -1215,7 +1225,7 @@ void FurnaceGUI::drawGBEnv(unsigned char vol, unsigned char len, unsigned char s
|
||||||
} \
|
} \
|
||||||
if (macroMode && displayModeName[0]!=NULL) { \
|
if (macroMode && displayModeName[0]!=NULL) { \
|
||||||
for (unsigned int m=0; displayModeName[m]!=NULL; m++) { \
|
for (unsigned int m=0; displayModeName[m]!=NULL; m++) { \
|
||||||
if (ImGui::RadioButton(displayModeName[m],macro.mode==m)) { \
|
if (ImGui::RadioButton(fmt::sprintf("%s##IOPMacroMode_%d%s",displayModeName[m],op,macro.name).c_str(),macro.mode==m)) { \
|
||||||
macro.mode=m; \
|
macro.mode=m; \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
|
@ -1301,7 +1311,7 @@ if (ImGui::BeginTable("MacroSpace",2)) { \
|
||||||
ImGui::Dummy(ImVec2(120.0f*dpiScale,dpiScale)); \
|
ImGui::Dummy(ImVec2(120.0f*dpiScale,dpiScale)); \
|
||||||
ImGui::TableNextColumn(); \
|
ImGui::TableNextColumn(); \
|
||||||
float availableWidth=ImGui::GetContentRegionAvail().x-reservedSpace; \
|
float availableWidth=ImGui::GetContentRegionAvail().x-reservedSpace; \
|
||||||
int totalFit=MIN(127,availableWidth/(16*dpiScale)); \
|
int totalFit=MIN(127,availableWidth/MAX(1,macroPointSize*dpiScale)); \
|
||||||
if (macroDragScroll>127-totalFit) { \
|
if (macroDragScroll>127-totalFit) { \
|
||||||
macroDragScroll=127-totalFit; \
|
macroDragScroll=127-totalFit; \
|
||||||
} \
|
} \
|
||||||
|
@ -2934,8 +2944,10 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
ex2Max=65535;
|
ex2Max=65535;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int panMin=0;
|
||||||
int panMax=0;
|
int panMax=0;
|
||||||
bool panSingle=false;
|
bool panSingle=false;
|
||||||
|
bool panSingleNoBit=false;
|
||||||
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_GB || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_VERA) {
|
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_GB || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_VERA) {
|
||||||
panMax=1;
|
panMax=1;
|
||||||
panSingle=true;
|
panSingle=true;
|
||||||
|
@ -2949,6 +2961,15 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
if (ins->type==DIV_INS_ES5506) {
|
if (ins->type==DIV_INS_ES5506) {
|
||||||
panMax=65535;
|
panMax=65535;
|
||||||
}
|
}
|
||||||
|
if (ins->type==DIV_INS_AMIGA && ins->std.panLMacro.mode) {
|
||||||
|
panMin=-16;
|
||||||
|
panMax=16;
|
||||||
|
}
|
||||||
|
if (ins->type==DIV_INS_SU) {
|
||||||
|
panMin=-127;
|
||||||
|
panMax=127;
|
||||||
|
panSingleNoBit=true;
|
||||||
|
}
|
||||||
|
|
||||||
if (settings.macroView==0) { // modern view
|
if (settings.macroView==0) { // modern view
|
||||||
MACRO_BEGIN(28*dpiScale);
|
MACRO_BEGIN(28*dpiScale);
|
||||||
|
@ -2974,8 +2995,18 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
if (panSingle) {
|
if (panSingle) {
|
||||||
NORMAL_MACRO(ins->std.panLMacro,0,2,"panL","Panning",32,ins->std.panLMacro.open,true,panBits,false,NULL,0,0,0,0,false,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],0,panMax,NULL,false);
|
NORMAL_MACRO(ins->std.panLMacro,0,2,"panL","Panning",32,ins->std.panLMacro.open,true,panBits,false,NULL,0,0,0,0,false,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],0,panMax,NULL,false);
|
||||||
} else {
|
} else {
|
||||||
NORMAL_MACRO(ins->std.panLMacro,0,panMax,"panL","Panning (left)",MIN(160,(31+panMax)),ins->std.panLMacro.open,false,NULL,false,NULL,0,0,0,0,false,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],0,panMax,NULL,false);
|
if (panSingleNoBit || (ins->type==DIV_INS_AMIGA && ins->std.panLMacro.mode)) {
|
||||||
NORMAL_MACRO(ins->std.panRMacro,0,panMax,"panR","Panning (right)",MIN(160,(31+panMax)),ins->std.panRMacro.open,false,NULL,false,NULL,0,0,0,0,false,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[14],0,panMax,NULL,false);
|
NORMAL_MACRO(ins->std.panLMacro,panMin,panMax,"panL","Panning",MIN(160,(31+panMax-panMin)),ins->std.panLMacro.open,false,NULL,false,NULL,0,0,0,0,(ins->type==DIV_INS_AMIGA),macroQSoundMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],panMin,panMax,NULL,false);
|
||||||
|
} else {
|
||||||
|
NORMAL_MACRO(ins->std.panLMacro,panMin,panMax,"panL","Panning (left)",MIN(160,(31+panMax-panMin)),ins->std.panLMacro.open,false,NULL,false,NULL,0,0,0,0,(ins->type==DIV_INS_AMIGA),macroQSoundMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],panMin,panMax,NULL,false);
|
||||||
|
}
|
||||||
|
if (!panSingleNoBit) {
|
||||||
|
if (ins->type==DIV_INS_AMIGA && ins->std.panLMacro.mode) {
|
||||||
|
NORMAL_MACRO(ins->std.panRMacro,0,1,"panR","Surround",32,ins->std.panRMacro.open,true,NULL,false,NULL,0,0,0,0,false,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[14],0,1,NULL,false);
|
||||||
|
} else {
|
||||||
|
NORMAL_MACRO(ins->std.panRMacro,panMin,panMax,"panR","Panning (right)",MIN(160,(31+panMax-panMin)),ins->std.panRMacro.open,false,NULL,false,NULL,0,0,0,0,false,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[14],panMin,panMax,NULL,false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NORMAL_MACRO(ins->std.pitchMacro,pitchMacroScroll,pitchMacroScroll+160,"pitch","Pitch",160,ins->std.pitchMacro.open,false,NULL,true,&pitchMacroScroll,-2048,2047,0,0,true,macroRelativeMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[15],-2048,2047,NULL,!ins->std.pitchMacro.mode);
|
NORMAL_MACRO(ins->std.pitchMacro,pitchMacroScroll,pitchMacroScroll+160,"pitch","Pitch",160,ins->std.pitchMacro.open,false,NULL,true,&pitchMacroScroll,-2048,2047,0,0,true,macroRelativeMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[15],-2048,2047,NULL,!ins->std.pitchMacro.mode);
|
||||||
|
|
|
@ -66,6 +66,22 @@ void FurnaceGUI::drawNewSong() {
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button("I'm feeling lucky")) {
|
||||||
|
if (sysCategories.size()==0) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
} else {
|
||||||
|
FurnaceGUISysCategory* newSystemCat=&sysCategories[rand()%sysCategories.size()];
|
||||||
|
if (newSystemCat->systems.size()==0) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
} else {
|
||||||
|
nextDesc=newSystemCat->systems[rand()%newSystemCat->systems.size()].definition.data();
|
||||||
|
accepted=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
|
||||||
if (ImGui::Button("Cancel")) {
|
if (ImGui::Button("Cancel")) {
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
|
@ -87,4 +103,4 @@ void FurnaceGUI::drawNewSong() {
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1333,6 +1333,17 @@ void FurnaceGUI::drawSampleEdit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
|
if (ctrlWheeling) {
|
||||||
|
double zoomPercent=100.0/sampleZoom;
|
||||||
|
zoomPercent+=wheelY*10.0;
|
||||||
|
if (zoomPercent>10000.0) zoomPercent=10000.0;
|
||||||
|
if (zoomPercent<1.0) zoomPercent=1.0;
|
||||||
|
sampleZoom=100.0/zoomPercent;
|
||||||
|
if (sampleZoom<0.01) sampleZoom=0.01;
|
||||||
|
sampleZoomAuto=false;
|
||||||
|
updateSampleTex=true;
|
||||||
|
}
|
||||||
|
|
||||||
int posX=-1;
|
int posX=-1;
|
||||||
int posY=0;
|
int posY=0;
|
||||||
ImVec2 pos=ImGui::GetMousePos();
|
ImVec2 pos=ImGui::GetMousePos();
|
||||||
|
|
Loading…
Reference in a new issue