diff --git a/README.md b/README.md index 6ae675deb..f4d4af3c3 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ some people have provided packages for Unix/Unix-like distributions. here's a li - **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt (warning: 0.5.8!). - **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. - **openSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. +- **Void Linux**: [furnace](https://github.com/void-linux/void-packages/tree/master/srcpkgs/furnace) is available in the official repository. --- # developer info diff --git a/demos/a2600/Coconut_Mall.fur b/demos/a2600/Coconut_Mall.fur deleted file mode 100644 index 471fcbec3..000000000 Binary files a/demos/a2600/Coconut_Mall.fur and /dev/null differ diff --git a/demos/arcade/Maximum_Overdrive_NamcoWSG.fur b/demos/arcade/Maximum_Overdrive_NamcoWSG.fur index 0fdbc113d..2f25c28ef 100644 Binary files a/demos/arcade/Maximum_Overdrive_NamcoWSG.fur and b/demos/arcade/Maximum_Overdrive_NamcoWSG.fur differ diff --git a/demos/arcade/Point_of_No_Return_SegaPCM.fur b/demos/arcade/Point_of_No_Return_SegaPCM.fur new file mode 100644 index 000000000..846d3680e Binary files /dev/null and b/demos/arcade/Point_of_No_Return_SegaPCM.fur differ diff --git a/demos/ay8930/PlayingOnTheStairs.fur b/demos/ay8930/PlayingOnTheStairs.fur index 0391fb4fe..80c5fd82a 100644 Binary files a/demos/ay8930/PlayingOnTheStairs.fur and b/demos/ay8930/PlayingOnTheStairs.fur differ diff --git a/demos/ay8930/joyful_.fur b/demos/ay8930/joyful.fur similarity index 100% rename from demos/ay8930/joyful_.fur rename to demos/ay8930/joyful.fur diff --git a/demos/misc/FiveTwoThreeTwo_MSM5232.fur b/demos/misc/FiveTwoThreeTwo_MSM5232.fur new file mode 100644 index 000000000..4c5efae9c Binary files /dev/null and b/demos/misc/FiveTwoThreeTwo_MSM5232.fur differ diff --git a/demos/a2600/atari breakbeat.fur b/demos/misc/atari_breakbeat_TIA.fur similarity index 100% rename from demos/a2600/atari breakbeat.fur rename to demos/misc/atari_breakbeat_TIA.fur diff --git a/demos/a2600/the_erfngjt.fur b/demos/misc/the_erfngjt_TIA.fur similarity index 100% rename from demos/a2600/the_erfngjt.fur rename to demos/misc/the_erfngjt_TIA.fur diff --git a/demos/msx/21492413.fur b/demos/msx/21492413.fur new file mode 100644 index 000000000..95669441d Binary files /dev/null and b/demos/msx/21492413.fur differ diff --git a/demos/msx/Il_ambreSong.fur b/demos/msx/ranburu_song.fur similarity index 100% rename from demos/msx/Il_ambreSong.fur rename to demos/msx/ranburu_song.fur diff --git a/doc/4-instrument/FM-ADSRchart.png b/doc/4-instrument/FM-ADSRchart.png new file mode 100644 index 000000000..407239c8d Binary files /dev/null and b/doc/4-instrument/FM-ADSRchart.png differ diff --git a/doc/4-instrument/fm.md b/doc/4-instrument/fm.md index 08542eb32..72c0ba781 100644 --- a/doc/4-instrument/fm.md +++ b/doc/4-instrument/fm.md @@ -1,47 +1,77 @@ # FM synthesis instrument editor -FM editor is divided into 7 tabs: +The FM editor is divided into 7 tabs: - **FM**: for controlling the basic parameters of FM sound source. - **Macros (FM)**: for macros controlling algorithm, feedback and LFO -- **Macros (OP1)**: for macros controlling FM paramets of operator 1 -- **Macros (OP2)**: for macros controlling FM paramets of operator 2 -- **Macros (OP3)**: for macros controlling FM paramets of operator 3 -- **Macros (OP4)**: for macros controlling FM paramets of operator 4 -- **Macros**: for miscellaneous macros controlling volume, argeggio and YM2151 noise generator. +- **Macros (OP1)**: for macros controlling FM parameters of operator 1 +- **Macros (OP2)**: for macros controlling FM parameters of operator 2 +- **Macros (OP3)**: for macros controlling FM parameters of operator 3 +- **Macros (OP4)**: for macros controlling FM parameters of operator 4 +- **Macros**: for miscellaneous macros controlling volume, arpeggio, and YM2151 noise generator. ## FM -FM synthesizers Furnace supports are four-operator, meaning it takes four oscillators to produce a single sound. Each operator is controlled by a dozen sliders: +The FM synthesizers Furnace supports are four-operator, meaning it takes four oscillators to produce a single sound. +These apply to the instrument as a whole: +- **Feedback (FB)**: Determines how many times operator 1 returns its output to itself. (0-7 range) +- **Algorithm (AL)**: Determines how operators are connected to each other. (0-7 range) + - Left-click pops up a small "operators changes with volume?" dialog where each operator can be toggled to scale with volume level. + - Right-click to switch to a preview display of the waveform generated on a new note: + - Left-click restarts the preview. + - Middle-click pauses and unpauses the preview. + - Right-click returns to algorithm view. + +- **LFO Frequency Sensitivity**: Determines the amount of LFO frequency changes. (0-7 range) +- **LFO Amplitude Sensitivity (AM)**: Determines the amount of LFO amplitude changes. (0-3 range) + +These apply to each operator: +- The crossed-arrows button can be dragged to rearrange operators. +- The **OP1**, **OP2**, **OP3**, and **OP4** buttons enable or disable those operators. +- **Amplitude Modulation (AM)**: Makes the operator affected by LFO. +- **Hardware Envelope Generator (SSG-EG)**: Executes the built-in envelope, inherited from AY-3-8910 PSG. Speed of execution is controlled via Decay Rate. YM2610/YM2612 sound source only. - **Attack Rate (AR)**: determines the rising time for the sound. The bigger the value, the faster the attack. (0-31 range) - **Decay Rate (DR)**: Determines the diminishing time for the sound. The higher the value, the shorter the decay. It's the initial amplitude decay rate. (0-31 range) -- **Secondary Decay Rate (DR2)/Sustain Rate (SR)**: Determines the diminishing time for the sound. The higher the value, the shorter the decay. This is the long "tail" of the sound that continues as long as the key is depressed. (0-31 range) +- **Sustain Level (SL)**: Determines the point at which the sound ceases to decay and changes to a sound having a constant level. The sustain level is expressed as a fraction of the maximum level. (0-15 range) +- **Secondary Decay Rate (DR2) / Sustain Rate (SR)**: Determines the diminishing time for the sound. The higher the value, the shorter the decay. This is the long "tail" of the sound that continues as long as the key is depressed. (0-31 range) - **Release Rate (RR)**: Determines the rate at which the sound disappears after KEY-OFF. The higher the value, the shorter the release. (0-15 range) -- **Sustain Level(SL)**: Determines the point at which the sound ceases to decay and changes to a sound having a constant level. The sustain level is expressed as a fraction of the maximum level. (0-15 range) - **Total Level (TL)**: Represents the envelope’s highest amplitude, with 0 being the largest and 127 (decimal) the smallest. A change of one unit is about 0.75 dB. -- **Envelope Scale (KSR)**: A parameter that determines the degree to which the envelope execution speed increases according to the pitch. (0-3 range) + +![FM ADSR chart](FM-ADSRchart.png) + +- **Envelope Scale (KSR)**: Also known as "Key Scale". Determines the degree to which the envelope execution speed increases according to the pitch. (0-3 range) - **Frequency Multiplier (MULT)**: Determines the operator frequency in relation to the pitch. (0-15 range) -- **Fine Detune (DT)**: Shifts the pitch a little (0-7 range) -- **Coarse Detune (DT2)**: Shifts the pitch by tens of cents (0-3 range) WARNING: this parameter affects only YM2151 sound source!!! -- **Hardware Envelope Generator (SSG-EG)**: Executes the built-in envelope, inherited from AY-3-8910 PSG. Speed of execution is controlled via Decay Rate. WARNING: this parameter affects only YM2610/YM2612 sound source!!! -- **Algorithm (AL)**: Determines how operators are connected to each other. (0-7 range) -- **Feedback (FB)**: Determines the amount of signal whick operator 1 returns to itself. (0-7 range) -- **Amplitude Modulation (AM)**: Makes the operator affected by LFO. -- **LFO Frequency Sensitivity**: Determines the amount of LFO frequency changes. (0-7 range) -- **LFO Amplitude Sensitivity (AM)**: Determines the amount of LFO frequency changes. (0-3 range) +- **Fine Detune (DT)**: Shifts the pitch a little. (0-7 range) +- **Coarse Detune (DT2)**: Shifts the pitch by tens of cents. (0-3 range) YM2151 sound source only. + + +## macros + +Macros define the sequence of values passed to the given parameter. Via macro, along with the previously mentioned parameters, the following can be controlled: + +## FM Macros + +- **AM Depth**: amplitude modulation depth. YM2151 sound source only. +- **PM Depth**: pitch modulation depth. YM2151 sound source only. +- **LFO Speed**: LFO frequency. +- **LFO Shape**: LFO shape. Choose between saw, square, triangle, and random. +- **OpMask**: toggles each operator. + +## OP1-OP4 Macros + +All parameters are listed above. ## Macros -Macros define the sequence of values passed to the given parameter. Via macro, aside previously mentioned parameters, the following can be controlled: - -- **LFO Frequency** -- **LFO Waveform**: _WARNING:_ this parameter affects only YM2151 sound source! -- **Amplitude Modulation Depth**: _WARNING:_ this parameter affects only YM2151 sound source! -- **Frequency Modulation Depth**: _WARNING:_ this parameter affects only YM2151 sound source! -- **Arpeggio Macro**: Pitch change sequence in semitones. Two modes are available: - - **Absolute** (default): Executes the pitch with absolute change based on the pitch of the actual note. - - **Fixed**: Executes at the pitch specified in the sequence regardless of the note pitch. +- **Arpeggio**: Pitch change sequence in semitones. - **Noise Frequency**: specifies the noise frequency in noise mode of YM2151's Channel 8 Operator 4 special mode. +- **Panning**: toggles output on left and right channels. +- **Pitch**: fine pitch. + - **Relative**: pitch changes are relative to the current pitch, not the note's base pitch. +- **Phase Reset**: Restarts all operators and resets the waveform to its start. Effectively the same as a `0Cxx` retrigger. -Looping: You can loop the execution of part of a sequence. Left-click anywhere on the Loop line at the bottom of the editor to create a loop. You can move the start and end points of the loop by dragging both ends of the loop. Rigkt-click to remove the loop. + +# links + +[FM instrument tutorial](https://www.youtube.com/watch?v=wS8edjurjDw): A great starting point to learn how create and work with FM sounds. This was made for DefleMask, but all the same principles apply. diff --git a/src/engine/engine.h b/src/engine/engine.h index 5c81f3e1c..87ff7116e 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -56,8 +56,8 @@ #define DIV_UNSTABLE -#define DIV_VERSION "dev164" -#define DIV_ENGINE_VERSION 164 +#define DIV_VERSION "dev165" +#define DIV_ENGINE_VERSION 165 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 77f3f3dc5..6e87a4431 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -2941,6 +2941,15 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } } + // Namco 163 pitch compensation compat + if (ds.version<165) { + for (int i=0; i>4)]&1)?((msm->vo16[i]*partVolume[3+(i&4)])>>8):0)+ - ((regPool[12+(i>>4)]&2)?((msm->vo8[i]*partVolume[2+(i&4)])>>8):0)+ - ((regPool[12+(i>>4)]&4)?((msm->vo4[i]*partVolume[1+(i&4)])>>8):0)+ - ((regPool[12+(i>>4)]&8)?((msm->vo2[i]*partVolume[i&4])>>8):0) + ((regPool[12+(i>>2)]&1)?((msm->vo16[i]*partVolume[3+(i&4)])>>8):0)+ + ((regPool[12+(i>>2)]&2)?((msm->vo8[i]*partVolume[2+(i&4)])>>8):0)+ + ((regPool[12+(i>>2)]&4)?((msm->vo4[i]*partVolume[1+(i&4)])>>8):0)+ + ((regPool[12+(i>>2)]&8)?((msm->vo2[i]*partVolume[i&4])>>8):0) )<<2; oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(o,-32768,32767); } diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 30061cebc..e24459ba9 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -34,7 +34,7 @@ rWriteMask(0x78-(c<<3)+(a&7),v,m) \ } -#define CHIP_FREQBASE (15*32768) +#define CHIP_FREQBASE (15*524288) const char* regCheatSheetN163[]={ "FreqL7", "40", @@ -256,7 +256,12 @@ void DivPlatformN163::tick(bool sysTick) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { // TODO: what is this mess? chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); - chan[i].freq=(((chan[i].freq*chan[i].curWaveLen)*(chanMax+1))/16); + if (lenCompensate) { + chan[i].freq=(((chan[i].freq*chan[i].curWaveLen)*(chanMax+1))/256); + } else { + chan[i].freq*=(chanMax+1); + chan[i].freq>>=3; + } if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff; if (chan[i].keyOn) { @@ -359,13 +364,13 @@ int DivPlatformN163::dispatch(DivCommand c) { int destFreq=NOTE_FREQUENCY(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value; + chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:16); if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value; + chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:16); if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; @@ -570,6 +575,8 @@ void DivPlatformN163::setFlags(const DivConfig& flags) { oscBuf[i]->rate=rate/(initChanMax+1); } + lenCompensate=flags.getBool("lenCompensate",false); + // needed to make sure changing channel count won't trigger glitches reset(); } diff --git a/src/engine/platform/n163.h b/src/engine/platform/n163.h index 3ebcbc441..c5ec64b7e 100644 --- a/src/engine/platform/n163.h +++ b/src/engine/platform/n163.h @@ -61,7 +61,7 @@ class DivPlatformN163: public DivDispatch { unsigned char initChanMax; unsigned char chanMax; short loadWave, loadPos; - bool multiplex; + bool multiplex, lenCompensate; n163_core n163; unsigned char regPool[128]; diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index d081fabc3..b0012c224 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -230,13 +230,16 @@ void DivPlatformOPLL::tick(bool sysTick) { if (i>=6 && properDrums) { drumState&=~(0x10>>(i-6)); immWrite(0x0e,0x20|drumState); + logV("properDrums %d",i); } else if (i>=6 && drums) { drumState&=~(0x10>>(chan[i].note%12)); immWrite(0x0e,0x20|drumState); + logV("drums %d",i); } else { if (i<9) { immWrite(0x20+i,(chan[i].freqH)|(chan[i].state.alg?0x20:0)); } + logV("normal %d",i); } //chan[i].keyOn=false; chan[i].keyOff=false; @@ -253,7 +256,7 @@ void DivPlatformOPLL::tick(bool sysTick) { for (int i=0; i<11; i++) { if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,octave(chan[i].baseFreq)*2,chan[i].pitch2,chipClock,CHIP_FREQBASE); - if (chan[i].fixedFreq>0) chan[i].freq=chan[i].fixedFreq; + if (chan[i].fixedFreq>0 && properDrums) chan[i].freq=chan[i].fixedFreq; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>65535) chan[i].freq=65535; int freqt=toFreq(chan[i].freq); @@ -771,11 +774,17 @@ int DivPlatformOPLL::dispatch(DivCommand c) { if (c.value) { properDrums=true; immWrite(0x0e,0x20); + drumState=0; } else { properDrums=false; immWrite(0x0e,0x00); drumState=0; } + chan[6].freqChanged=true; + chan[7].freqChanged=true; + chan[8].freqChanged=true; + chan[9].freqChanged=true; + chan[10].freqChanged=true; break; case DIV_CMD_MACRO_OFF: chan[c.chan].std.mask(c.value,true); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index a95b58b8b..18d0dadfe 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -962,6 +962,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo int clockSel=flags.getInt("clockSel",0); int channels=flags.getInt("channels",0)+1; bool multiplex=flags.getBool("multiplex",false); + bool lenCompensate=flags.getBool("lenCompensate",false); ImGui::Text("Clock rate:"); if (ImGui::RadioButton("NTSC (1.79MHz)",clockSel==0)) { @@ -985,12 +986,16 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo if (ImGui::Checkbox("Disable hissing",&multiplex)) { altered=true; } + if (ImGui::Checkbox("Scale frequency to wave length",&lenCompensate)) { + altered=true; + } if (altered) { e->lockSave([&]() { flags.set("clockSel",clockSel); flags.set("channels",channels-1); flags.set("multiplex",multiplex); + flags.set("lenCompensate",lenCompensate); }); } break;