QSound: implement panning macro - PLEASE READ

the panning strategy for QSound has changed!
it's now 08xy where x is left and y is right (muting is not possible though!)
this makes it consistent with other chips, plus QSound's pan range was
32 anyway

in order to toggle the QSound effect use effect 12xx
This commit is contained in:
tildearrow 2022-04-28 23:58:11 -05:00
parent 2ac0e8af42
commit 5567746e0b
10 changed files with 87 additions and 11 deletions

View file

@ -1,7 +1,5 @@
# 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

View file

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

View file

@ -125,6 +125,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,

View file

@ -1041,6 +1041,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;
@ -1473,6 +1495,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();

View file

@ -475,6 +475,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);

View file

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

View file

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

View file

@ -127,6 +127,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",
@ -434,6 +435,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));

View file

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

View file

@ -212,6 +212,7 @@ const char* dualWSEffects[7]={
const char* macroAbsoluteMode="Fixed"; const char* macroAbsoluteMode="Fixed";
const char* macroRelativeMode="Relative"; const char* macroRelativeMode="Relative";
const char* macroQSoundMode="QSound";
const char* macroDummyMode="Bug"; const char* macroDummyMode="Bug";
@ -2839,8 +2840,10 @@ void FurnaceGUI::drawInsEdit() {
} }
if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (ins->type==DIV_INS_SAA1099) ex1Max=8;
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;
@ -2851,6 +2854,15 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_PCE || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_SAA1099) { if (ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_PCE || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_SAA1099) {
panMax=15; panMax=15;
} }
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);
@ -2872,8 +2884,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,0,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,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],0,panMax,NULL,false);
} else { } else {
NORMAL_MACRO(ins->std.panLMacro,0,panMax,"panL","Panning (left)",(31+panMax),ins->std.panLMacro.open,false,NULL,false,NULL,0,0,0,0,false,0,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)",(31+panMax),ins->std.panRMacro.open,false,NULL,false,NULL,0,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[14],0,panMax,NULL,false); NORMAL_MACRO(ins->std.panLMacro,panMin,panMax,"panL","Panning",(31+panMax-panMin),ins->std.panLMacro.open,false,NULL,false,NULL,0,0,0,0,(ins->type==DIV_INS_AMIGA),(ins->type==DIV_INS_AMIGA?1:0),macroQSoundMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[13],panMin,panMax,NULL,false);
} else {
NORMAL_MACRO(ins->std.panLMacro,panMin,panMax,"panL","Panning (left)",(31+panMax-panMin),ins->std.panLMacro.open,false,NULL,false,NULL,0,0,0,0,(ins->type==DIV_INS_AMIGA),(ins->type==DIV_INS_AMIGA?1:0),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,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[14],0,1,NULL,false);
} else {
NORMAL_MACRO(ins->std.panRMacro,panMin,panMax,"panR","Panning (right)",(31+panMax-panMin),ins->std.panRMacro.open,false,NULL,false,NULL,0,0,0,0,false,0,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,1,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,1,macroRelativeMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[15],-2048,2047,NULL,!ins->std.pitchMacro.mode);