Allow Left waveform can be invertable,

Improvement documents
This commit is contained in:
cam900 2022-03-09 00:50:10 +09:00
parent da73c365e4
commit 6c432bc42e
7 changed files with 110 additions and 60 deletions

View File

@ -10,7 +10,7 @@ double-click to open the instrument editor.
every instrument can be renamed and have its type changed.
depending on the instrument type, there are currently 12 different types of an instrument editor:
depending on the instrument type, there are currently 13 different types of an instrument editor:
- [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610.
- [Standard](standard.md) - for use with NES and Sega Master System's PSG sound source and its derivatives.

View File

@ -2,4 +2,4 @@
Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy.
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms as of now, with 16-level height for GB and WS, and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit within the constraints of the system.
Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope and WS, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system.

View File

@ -10,6 +10,18 @@ Wavetable needs to paired with envelope, this feature is similar as AY PSG but i
In furnace, this chip is can be configurable for original arcade mono output or stereo output - its simulates early 'incorrect' emulation on some mono hardware but it is also based on the assumption that each channel is connected to each output.
# waveform type
This chip supports 2 type waveforms, needs to paired external 8KB RAM for use these feature:
One is signed 8 bit mono waveform, its operated like other wavetable based sound systems.
These are stored at bottom half of RAM at common case.
Another one ("Envelope") is 4 bit stereo waveform, its multiplied with above and calculates final output, Each nibble is used for each output channels.
These are stored at upper half of RAM at common case.
Both waveforms are 128 byte fixed size, its freely allocated at each half of RAM except channel register area: each half can be stored total 32/31 waveforms at once.
# effects
- `10xx`: change wave.
@ -20,8 +32,8 @@ In furnace, this chip is can be configurable for original arcade mono output or
- bit 0 sets whether envelope will affect this channel.
- bit 1 sets whether envelope one-shot mode. when it sets, channel is halted after envelope cycle is ended.
- bit 2 sets whether envelope shape split mode. when it sets, envelope shape will splitted to left half and right half.
- bit 3 sets whether the right shape will mirror the left one.
- bit 4 sets whether the right output will mirror the left one.
- bit 3/5 sets whether the right/left shape will mirror the original one.
- bit 4/6 sets whether the right/left output will mirror the original one.
- `23xx`: set envelope period.
- `25xx`: slide envelope period up.
- `26xx`: slide envelope period down.

View File

@ -219,7 +219,7 @@ const char* DivPlatformX1_010::getEffectName(unsigned char effect) {
return "20xx: Set PCM frequency (1 to FF)";
break;
case 0x22:
return "22xx: Set envelope mode (bit 0: enable, bit 1: one-shot, bit 2: split shape to L/R, bit 3: H.invert right, bit 4: V.invert right)";
return "22xx: Set envelope mode (bit 0: enable, bit 1: one-shot, bit 2: split shape to L/R, bit 3/5: H.invert right/left, bit 4/6: V.invert right/left)";
break;
case 0x23:
return "23xx: Set envelope period";
@ -258,8 +258,8 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l
double DivPlatformX1_010::NoteX1_010(int ch, int note) {
if (chan[ch].pcm) { // PCM note
double off=1.0;
int sample = chan[ch].sample;
if (sample>=0 && sample<parent->song.sampleLen) {
int sample=chan[ch].sample;
if (sample>=0&&sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(sample);
if (s->centerRate<1) {
off=1.0;
@ -279,7 +279,7 @@ void DivPlatformX1_010::updateWave(int ch) {
chan[ch].waveBank ^= 1;
}
for (int i=0; i<128; i++) {
if (wt->max<1 || wt->len<1) {
if (wt->max<1||wt->len<1) {
waveWrite(ch,i,0);
} else {
waveWrite(ch,i,wt->data[i*wt->len/128]*255/wt->max);
@ -304,23 +304,25 @@ void DivPlatformX1_010::updateEnvelope(int ch) {
} else {
DivWavetable* wt=parent->getWave(chan[ch].env.shape);
for (int i=0; i<128; i++) {
if (wt->max<1 || wt->len<1) {
if (wt->max<1||wt->len<1) {
envFill(ch,i);
} else if (chan[ch].env.flag.envHinv||chan[ch].env.flag.envSplit||chan[ch].env.flag.envVinv) { // Stereo config
int la = i, ra = i;
int lo, ro;
if (chan[ch].env.flag.envHinv) { ra = 127-i; } // horizontal invert right envelope
} else if (chan[ch].env.flag.envSplit||chan[ch].env.flag.envHinvR||chan[ch].env.flag.envVinvR||chan[ch].env.flag.envHinvL||chan[ch].env.flag.envVinvL) { // Stereo config
int la=i,ra=i;
int lo,ro;
if (chan[ch].env.flag.envHinvR) { ra=127-i; } // horizontal invert right envelope
if (chan[ch].env.flag.envHinvL) { la=127-i; } // horizontal invert left envelope
if (chan[ch].env.flag.envSplit) { // Split shape to left and right half
lo = wt->data[la*(wt->len/128/2)]*15/wt->max;
ro = wt->data[(ra+128)*(wt->len/128/2)]*15/wt->max;
lo=wt->data[la*(wt->len/128/2)]*15/wt->max;
ro=wt->data[(ra+128)*(wt->len/128/2)]*15/wt->max;
} else {
lo = wt->data[la*wt->len/128]*15/wt->max;
ro = wt->data[ra*wt->len/128]*15/wt->max;
lo=wt->data[la*wt->len/128]*15/wt->max;
ro=wt->data[ra*wt->len/128]*15/wt->max;
}
if (chan[ch].env.flag.envVinv) { ro = 15-ro; } // vertical invert right envelope
if (chan[ch].env.flag.envVinvR) { ro=15-ro; } // vertical invert right envelope
if (chan[ch].env.flag.envVinvL) { lo=15-lo; } // vertical invert left envelope
envWrite(ch,i,lo,ro);
} else {
int out = wt->data[i*wt->len/128]*15/wt->max;
int out=wt->data[i*wt->len/128]*15/wt->max;
envWrite(ch,i,out,out);
}
}
@ -342,7 +344,7 @@ void DivPlatformX1_010::tick() {
chan[i].envChanged=true;
}
}
if ((!chan[i].pcm) || chan[i].furnacePCM) {
if ((!chan[i].pcm)||chan[i].furnacePCM) {
if (chan[i].std.hadArp) {
if (!chan[i].inPorta) {
if (chan[i].std.arpMode) {
@ -353,13 +355,13 @@ void DivPlatformX1_010::tick() {
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arpMode && chan[i].std.finishedArp) {
if (chan[i].std.arpMode&&chan[i].std.finishedArp) {
chan[i].baseFreq=NoteX1_010(i,chan[i].note);
chan[i].freqChanged=true;
}
}
}
if (chan[i].std.hadWave && !chan[i].pcm) {
if (chan[i].std.hadWave&&!chan[i].pcm) {
if (chan[i].wave!=chan[i].std.wave) {
chan[i].wave=chan[i].std.wave;
if (!chan[i].pcm) {
@ -389,21 +391,35 @@ void DivPlatformX1_010::tick() {
bool nextSplit=(chan[i].std.ex1&4);
if (nextSplit!=(chan[i].env.flag.envSplit)) {
chan[i].env.flag.envSplit=nextSplit;
if (!isMuted[i] && !chan[i].pcm) {
if (!isMuted[i]&&!chan[i].pcm) {
chan[i].envChanged=true;
}
}
bool nextHinv=(chan[i].std.ex1&8);
if (nextHinv!=(chan[i].env.flag.envHinv)) {
chan[i].env.flag.envHinv=nextHinv;
if (!isMuted[i] && !chan[i].pcm) {
bool nextHinvR=(chan[i].std.ex1&8);
if (nextHinvR!=(chan[i].env.flag.envHinvR)) {
chan[i].env.flag.envHinvR=nextHinvR;
if (!isMuted[i]&&!chan[i].pcm) {
chan[i].envChanged=true;
}
}
bool nextVinv=(chan[i].std.ex1&16);
if (nextVinv!=(chan[i].env.flag.envVinv)) {
chan[i].env.flag.envVinv=nextVinv;
if (!isMuted[i] && !chan[i].pcm) {
bool nextVinvR=(chan[i].std.ex1&16);
if (nextVinvR!=(chan[i].env.flag.envVinvR)) {
chan[i].env.flag.envVinvR=nextVinvR;
if (!isMuted[i]&&!chan[i].pcm) {
chan[i].envChanged=true;
}
}
bool nextHinvL=(chan[i].std.ex1&32);
if (nextHinvL!=(chan[i].env.flag.envHinvL)) {
chan[i].env.flag.envHinvL=nextHinvL;
if (!isMuted[i]&&!chan[i].pcm) {
chan[i].envChanged=true;
}
}
bool nextVinvL=(chan[i].std.ex1&64);
if (nextVinvL!=(chan[i].env.flag.envVinvL)) {
chan[i].env.flag.envVinvL=nextVinvL;
if (!isMuted[i]&&!chan[i].pcm) {
chan[i].envChanged=true;
}
}
@ -412,7 +428,7 @@ void DivPlatformX1_010::tick() {
if (chan[i].env.shape!=chan[i].std.ex2) {
chan[i].env.shape=chan[i].std.ex2;
if (!chan[i].pcm) {
if (chan[i].env.flag.envEnable && (!isMuted[i])) {
if (chan[i].env.flag.envEnable&&(!isMuted[i])) {
chan[i].envChanged=true;
}
if (!chan[i].keyOff) chan[i].keyOn=true;
@ -441,7 +457,7 @@ void DivPlatformX1_010::tick() {
updateEnvelope(i);
chan[i].envChanged=false;
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
if (chan[i].freqChanged||chan[i].keyOn||chan[i].keyOff) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false);
if (chan[i].pcm) {
if (chan[i].freq<1) chan[i].freq=1;
@ -451,12 +467,12 @@ void DivPlatformX1_010::tick() {
if (chan[i].freq>65535) chan[i].freq=65535;
chWrite(i,2,chan[i].freq&0xff);
chWrite(i,3,(chan[i].freq>>8)&0xff);
if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) {
if (chan[i].freqChanged&&chan[i].autoEnvNum>0&&chan[i].autoEnvDen>0) {
chan[i].env.period=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>12;
chWrite(i,4,chan[i].env.period);
}
}
if (chan[i].keyOn || chan[i].keyOff || (chRead(i,0)&1)) {
if (chan[i].keyOn||chan[i].keyOff||(chRead(i,0)&1)) {
refreshControl(i);
}
if (chan[i].keyOn) chan[i].keyOn=false;
@ -492,7 +508,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: {
chWrite(c.chan,0,0); // reset previous note
DivInstrument* ins=parent->getIns(chan[c.chan].ins);
if ((ins->type==DIV_INS_AMIGA) || chan[c.chan].pcm) {
if ((ins->type==DIV_INS_AMIGA)||chan[c.chan].pcm) {
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].furnacePCM=true;
} else {
@ -503,7 +519,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
chan[c.chan].pcm=true;
chan[c.chan].std.init(ins);
chan[c.chan].sample=ins->amiga.initSample;
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
if (chan[c.chan].sample>=0&&chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
chWrite(c.chan,4,(s->offX1_010>>12)&0xff);
int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded
@ -566,7 +582,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
if (chan[c.chan].ins!=c.value||c.value2==1) {
chan[c.chan].ins=c.value;
}
break;
@ -658,11 +674,11 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
}
case DIV_CMD_LEGATO:
chan[c.chan].note=c.value;
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0)));
chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note+((chan[c.chan].std.willArp&&!chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0)));
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (chan[c.chan].active&&c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins));
}
chan[c.chan].inPorta=c.value;
@ -697,21 +713,35 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
bool nextSplit=c.value&4;
if (nextSplit!=(chan[c.chan].env.flag.envSplit)) {
chan[c.chan].env.flag.envSplit=nextSplit;
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
if (!isMuted[c.chan]&&!chan[c.chan].pcm) {
chan[c.chan].envChanged=true;
}
}
bool nextHinv=c.value&8;
if (nextHinv!=(chan[c.chan].env.flag.envHinv)) {
chan[c.chan].env.flag.envHinv=nextHinv;
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
bool nextHinvR=c.value&8;
if (nextHinvR!=(chan[c.chan].env.flag.envHinvR)) {
chan[c.chan].env.flag.envHinvR=nextHinvR;
if (!isMuted[c.chan]&&!chan[c.chan].pcm) {
chan[c.chan].envChanged=true;
}
}
bool nextVinv=c.value&16;
if (nextVinv!=(chan[c.chan].env.flag.envVinv)) {
chan[c.chan].env.flag.envVinv=nextVinv;
if (!isMuted[c.chan] && !chan[c.chan].pcm) {
bool nextVinvR=c.value&16;
if (nextVinvR!=(chan[c.chan].env.flag.envVinvR)) {
chan[c.chan].env.flag.envVinvR=nextVinvR;
if (!isMuted[c.chan]&&!chan[c.chan].pcm) {
chan[c.chan].envChanged=true;
}
}
bool nextHinvL=c.value&32;
if (nextHinvL!=(chan[c.chan].env.flag.envHinvL)) {
chan[c.chan].env.flag.envHinvL=nextHinvL;
if (!isMuted[c.chan]&&!chan[c.chan].pcm) {
chan[c.chan].envChanged=true;
}
}
bool nextVinvL=c.value&64;
if (nextVinvL!=(chan[c.chan].env.flag.envVinvL)) {
chan[c.chan].env.flag.envVinvL=nextVinvL;
if (!isMuted[c.chan]&&!chan[c.chan].pcm) {
chan[c.chan].envChanged=true;
}
}

View File

@ -44,21 +44,27 @@ class DivPlatformX1_010: public DivDispatch {
unsigned char envEnable : 1;
unsigned char envOneshot : 1;
unsigned char envSplit : 1;
unsigned char envHinv : 1;
unsigned char envVinv : 1;
unsigned char envHinvR : 1;
unsigned char envVinvR : 1;
unsigned char envHinvL : 1;
unsigned char envVinvL : 1;
void reset() {
envEnable=0;
envOneshot=0;
envSplit=0;
envHinv=0;
envVinv=0;
envHinvR=0;
envVinvR=0;
envHinvL=0;
envVinvL=0;
}
EnvFlag():
envEnable(0),
envOneshot(0),
envSplit(0),
envHinv(0),
envVinv(0) {}
envHinvR(0),
envVinvR(0),
envHinvL(0),
envVinvL(0) {}
};
int shape, period, slide, slidefrac;
EnvFlag flag;

View File

@ -268,8 +268,10 @@ void putDispatchChan(void* data, int chanNum, int type) {
ImGui::TextColored(ch->env.flag.envEnable?colorOn:colorOff,">> EnvEnable");
ImGui::TextColored(ch->env.flag.envOneshot?colorOn:colorOff,">> EnvOneshot");
ImGui::TextColored(ch->env.flag.envSplit?colorOn:colorOff,">> EnvSplit");
ImGui::TextColored(ch->env.flag.envHinv?colorOn:colorOff,">> EnvHinv");
ImGui::TextColored(ch->env.flag.envVinv?colorOn:colorOff,">> EnvVinv");
ImGui::TextColored(ch->env.flag.envHinvR?colorOn:colorOff,">> EnvHinvR");
ImGui::TextColored(ch->env.flag.envVinvR?colorOn:colorOff,">> EnvVinvR");
ImGui::TextColored(ch->env.flag.envHinvL?colorOn:colorOff,">> EnvHinvL");
ImGui::TextColored(ch->env.flag.envVinvL?colorOn:colorOff,">> EnvVinvL");
break;
}
default:

View File

@ -150,8 +150,8 @@ const char* mikeyFeedbackBits[11] = {
"0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL
};
const char* x1_010EnvBits[6]={
"enable", "oneshot", "split L/R", "Hinv", "Vinv", NULL
const char* x1_010EnvBits[8]={
"enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL
};
const char* oneBit[2]={
@ -1411,7 +1411,7 @@ void FurnaceGUI::drawInsEdit() {
}
if (ins->type==DIV_INS_X1_010) {
dutyMax=0;
ex1Max=5;
ex1Max=7;
ex2Max=63;
ex2Bit=false;
}