diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index 2b907634..9a31cf65 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -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. diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index 00573925..5e234bf6 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -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. diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index 7d577871..e2e44f18 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -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. diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 331b3842..e6b07d01 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -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 && samplesong.sampleLen) { + int sample=chan[ch].sample; + if (sample>=0&&samplesong.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].samplesong.sampleLen) { + if (chan[c.chan].sample>=0&&chan[c.chan].samplesong.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; } } diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 8e03109b..cb9ad7b4 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -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; diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index fc2d51ea..ddbfb5b5 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -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: diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 0b7ce0ab..260713e9 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -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; }