From d4d1ade5134a5d49da216f8df034a5055dd70b62 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 3 Mar 2022 03:10:04 +0900 Subject: [PATCH 001/105] Add various system, Minor corrections YM2413 (drums mode) Standalone YM2413 with allows drum channel. Sound Expander for Commodore 64 OPL FM Sound expander cartridge for Commodore 64, it's placeholder until OPL is implemented. MSX-MUSIC: MSX's sound standard, appeared after MSX-AUDIO. it's basically OPLL FM sound expansion for MSX. SSI 2001: ISA Sound card with SID 6581. SID input clock is driven from ISA clock, so I modified flags value check routine. Sound Blaster w/Game Blaster Compatible Earliest Sound Blaster models has featured with Game Blaster compatiblity, It's has 2 SAA1099s like CMS/Game Blaster. It's removed at later models, but some hardware has just empty socket; you can restore this feature when you mount SAA1099 at empty socket. Sharp X1: Predecessor of X68000. it has built in AY PSG like competitors of the same period, but it has YM2151 FM sound addon in later models. FM sound is embedded in turbo Z, and that is succeeded by X68000. X68000 hasn't AY, instead OKI MSM6258. YM2151 in OutRun Board and X Board is 4MHz --- src/engine/platform/c64.cpp | 15 +++-- src/gui/gui.cpp | 113 ++++++++++++++++++++++++++++++++++-- 2 files changed, 119 insertions(+), 9 deletions(-) diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 6257daead..889211332 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -509,10 +509,17 @@ void DivPlatformC64::setChipModel(bool is6581) { } void DivPlatformC64::setFlags(unsigned int flags) { - if (flags&1) { - rate=COLOR_PAL*2.0/9.0; - } else { - rate=COLOR_NTSC*2.0/7.0; + switch (flags&0xf) { + case 0x0: // NTSC C64 + rate=COLOR_NTSC*2.0/7.0; + break; + case 0x1: // PAL C64 + rate=COLOR_PAL*2.0/9.0; + break; + case 0x2: // SSI 2001 + default: + rate=14318180.0/16.0; + break; } chipClock=rate; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 0aeeb129e..5b4fc8134 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4714,7 +4714,7 @@ bool FurnaceGUI::loop() { e->setSysFlags(i,1,restart); updateWindowTitle(); } - if (ImGui::RadioButton("X68000 (4MHz)",flags==2)) { + if (ImGui::RadioButton("X1/X68000 (4MHz)",flags==2)) { e->setSysFlags(i,2,restart); updateWindowTitle(); } @@ -4733,6 +4733,21 @@ bool FurnaceGUI::loop() { updateWindowTitle(); } break; + case DIV_SYSTEM_C64_8580: + case DIV_SYSTEM_C64_6581: + if (ImGui::RadioButton("NTSC (1.02MHz)",flags==0)) { + e->setSysFlags(i,0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (0.99MHz)",flags==1)) { + e->setSysFlags(i,1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("SSI 2001 (0.89MHz)",flags==2)) { + e->setSysFlags(i,2,restart); + updateWindowTitle(); + } + break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: { ImGui::Text("Clock rate:"); @@ -4748,7 +4763,7 @@ bool FurnaceGUI::loop() { e->setSysFlags(i,(flags&(~15))|2,restart); updateWindowTitle(); } - if (ImGui::RadioButton("2MHz (Atari ST)",(flags&15)==3)) { + if (ImGui::RadioButton("2MHz (Atari ST/Sharp X1)",(flags&15)==3)) { e->setSysFlags(i,(flags&(~15))|3,restart); updateWindowTitle(); } @@ -6157,6 +6172,12 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2413 (drums mode)", { + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Square"); @@ -6331,6 +6352,35 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + /* + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID + Sound Expander)", { + DIV_SYSTEM_OPL, 64, 0, 0, + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID + Sound Expander with drums mode)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID + Sound Expander)", { + DIV_SYSTEM_OPL, 64, 0, 0, + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID + Sound Expander with drums mode)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + ));*/ cat.systems.push_back(FurnaceGUISysDef( "Amiga", { DIV_SYSTEM_AMIGA, 64, 0, 0, @@ -6343,6 +6393,20 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + MSX-MUSIC", { + DIV_SYSTEM_OPLL, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + MSX-MUSIC (drums mode)", { + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "ZX Spectrum (48K)", { DIV_SYSTEM_AY8910, 64, 0, 2, @@ -6386,6 +6450,13 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + SSI 2001", { + DIV_SYSTEM_C64_6581, 64, 0, 2, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "PC + Game Blaster", { DIV_SYSTEM_SAA1099, 64, -127, 1, @@ -6408,6 +6479,24 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster w/Game Blaster Compatible", { + DIV_SYSTEM_OPL2, 64, 0, 0, + DIV_SYSTEM_SAA1099, 64, -127, 1, + DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster w/Game Blaster Compatible (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, + DIV_SYSTEM_SAA1099, 64, -127, 1, + DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "PC + Sound Blaster Pro 2", { DIV_SYSTEM_OPL3, 64, 0, 0, @@ -6422,10 +6511,24 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Sharp X1", { + DIV_SYSTEM_AY8910, 64, 0, 3, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sharp X1 + FM Addon", { + DIV_SYSTEM_AY8910, 64, 0, 3, + DIV_SYSTEM_YM2151, 64, 0, 2, + 0 + } + )); /* cat.systems.push_back(FurnaceGUISysDef( "Sharp X68000", { - DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_YM2151, 64, 0, 2, + DIV_SYSTEM_MSM6258, 64, 0, 0, 0 } ));*/ @@ -6448,7 +6551,7 @@ FurnaceGUI::FurnaceGUI(): )); cat.systems.push_back(FurnaceGUISysDef( "Sega OutRun/X Board", { - DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_YM2151, 64, 0, 2, DIV_SYSTEM_SEGAPCM, 64, 0, 0, 0 } @@ -6552,7 +6655,7 @@ FurnaceGUI::FurnaceGUI(): )); cat.systems.push_back(FurnaceGUISysDef( "Arcade (YM2151 and SegaPCM)", { - DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_YM2151, 64, 0, 2, DIV_SYSTEM_SEGAPCM_COMPAT, 64, 0, 0, 0 } From 87561bf9cfd7d312aadce928e11535613d522384 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 3 Mar 2022 03:12:10 +0900 Subject: [PATCH 002/105] Fix spacing --- src/engine/platform/c64.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 889211332..49be60fb8 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -510,16 +510,16 @@ void DivPlatformC64::setChipModel(bool is6581) { void DivPlatformC64::setFlags(unsigned int flags) { switch (flags&0xf) { - case 0x0: // NTSC C64 + case 0x0: // NTSC C64 rate=COLOR_NTSC*2.0/7.0; - break; - case 0x1: // PAL C64 + break; + case 0x1: // PAL C64 rate=COLOR_PAL*2.0/9.0; - break; - case 0x2: // SSI 2001 - default: + break; + case 0x2: // SSI 2001 + default: rate=14318180.0/16.0; - break; + break; } chipClock=rate; } From c4f2090b48607c1773b748b0e0cacd8b51004262 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 3 Mar 2022 13:07:16 +0900 Subject: [PATCH 003/105] Deflemask compatibility --- src/gui/gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 5b4fc8134..dd2349c25 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6655,7 +6655,7 @@ FurnaceGUI::FurnaceGUI(): )); cat.systems.push_back(FurnaceGUISysDef( "Arcade (YM2151 and SegaPCM)", { - DIV_SYSTEM_YM2151, 64, 0, 2, + DIV_SYSTEM_YM2151, 64, 0, 0, DIV_SYSTEM_SEGAPCM_COMPAT, 64, 0, 0, 0 } From 5393b67c1d5e0bc3698e2f8e80b3ff7832f791da Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 3 Mar 2022 16:03:40 +0900 Subject: [PATCH 004/105] Yamaha SFG-01 OPM FM sound expansion by Yamaha, for their CX series MSX computers: It's needs converter when connect it to standard MSX cartridge slot. Successor is SFG-05, It has YM2164 OPP instead YM2151 OPM. --- src/gui/gui.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index dd2349c25..e9e75980a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6393,6 +6393,13 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + SFG-01", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "MSX + MSX-MUSIC", { DIV_SYSTEM_OPLL, 64, 0, 0, From 9abf872ff3ef08633d3a6c324bc27b097d4d78bc Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Fri, 4 Mar 2022 18:13:49 +0700 Subject: [PATCH 005/105] Add VERA support for Commander X16 --- CMakeLists.txt | 1 + papers/doc/4-instrument/README.md | 1 + papers/doc/4-instrument/vera.md | 8 + src/engine/dispatchContainer.cpp | 4 + src/engine/engine.cpp | 2 + src/engine/instrument.h | 1 + src/engine/platform/vera.cpp | 404 ++++++++++++++++++++++++++++++ src/engine/platform/vera.h | 74 ++++++ src/engine/playback.cpp | 12 + src/engine/song.h | 3 +- src/engine/sysDef.cpp | 36 ++- src/gui/gui.cpp | 4 +- src/gui/insEdit.cpp | 28 ++- 13 files changed, 565 insertions(+), 13 deletions(-) create mode 100644 papers/doc/4-instrument/vera.md create mode 100644 src/engine/platform/vera.cpp create mode 100644 src/engine/platform/vera.h diff --git a/CMakeLists.txt b/CMakeLists.txt index af3e3a7d1..7211eb3d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,6 +303,7 @@ src/engine/platform/saa.cpp src/engine/platform/amiga.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp +src/engine/platform/vera.cpp src/engine/platform/dummy.cpp src/engine/platform/lynx.cpp ) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index b9d4b8f40..499a5ee14 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -23,6 +23,7 @@ depending on the instrument type, there are currently 10 different types of an i - [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610. - [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM and PC Engine's sample playback mode. - [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console. +- [VERA](vera.md) - for use with Commander X16 VERA. # macros diff --git a/papers/doc/4-instrument/vera.md b/papers/doc/4-instrument/vera.md new file mode 100644 index 000000000..b577ccd23 --- /dev/null +++ b/papers/doc/4-instrument/vera.md @@ -0,0 +1,8 @@ +# VERA instrument editor + +VERA instrument editor consists of only four macros: + +- [Volume] - volume sequence +- [Arpeggio] - pitch sequence +- [Duty cycle] - pulse duty cycle sequence +- [Waveform] - select the waveform used by instrument diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 8398ca3d0..e34eebbb8 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -39,6 +39,7 @@ #include "platform/amiga.h" #include "platform/segapcm.h" #include "platform/qsound.h" +#include "platform/vera.h" #include "platform/dummy.h" #include "platform/lynx.h" #include "../ta-log.h" @@ -226,6 +227,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SEGAPCM_COMPAT: dispatch=new DivPlatformSegaPCM; break; + case DIV_SYSTEM_VERA: + dispatch = new DivPlatformVERA; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a8b023d7d..4f387b272 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -951,6 +951,8 @@ int DivEngine::getEffectiveSampleRate(int rate) { return (24038*MIN(65535,(rate*4096/24038)))/4096; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: return 18518; + case DIV_SYSTEM_VERA: + return (48828*MIN(128,(rate*128/48828)))/128; default: break; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 27ce2a4ce..5f11df65a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -48,6 +48,7 @@ enum DivInstrumentType { DIV_INS_BEEPER=21, DIV_INS_SWAN=22, DIV_INS_MIKEY=23, + DIV_INS_VERA=24, }; // FM operator structure: diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp new file mode 100644 index 000000000..47f4888d6 --- /dev/null +++ b/src/engine/platform/vera.cpp @@ -0,0 +1,404 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "vera.h" +#include "../engine.h" +#include +#include + +#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d);} +#define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f)) +#define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0)) +#define rWriteFIFOVol(d) rWrite(16,0,(regPool[64]&(~0x3f))|((d)&0x3f)) + +const char* regCheatSheetVERA[]={ + "CHxFreq", "00+x*4", + "CHxVol", "02+x*4", + "CHxWave", "03+x*4", + + "AUDIO_CTRL", "40", + "AUDIO_RATE", "41", + + NULL +}; + +const char** DivPlatformVERA::getRegisterSheet() { + return regCheatSheetVERA; +} + +const char* DivPlatformVERA::getEffectName(unsigned char effect) { + switch (effect) { + case 0x20: + return "20xx: Change waveform"; + break; + case 0x22: + return "22xx: Set duty cycle (0 to 63)"; + break; + } + return NULL; +} + +void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { + // taken from the official X16 emulator's source code, (c) 2020 Frank van den Hoef + const uint8_t volume_lut_psg[64]={0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 52, 56, 59, 63}; + const uint8_t volume_lut_pcm[16]={0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64}; + for (size_t pos=start; pos>6) { + case 0: val=(new_accum>>10)>(regPool[i*4+3]&(unsigned)0x3f)?0:63; break; + case 1: val=(new_accum>>11); break; + case 2: val=(new_accum&0x10000)?(~(new_accum>>10)&0x3f):((new_accum>>10)&0x3f); break; + case 3: val=chan[i].noiseval; break; + } + val=(val-0x20)*volume_lut_psg[regPool[i*4+2]&0x3f]; + lout+=(regPool[i*4+2]&0x40)?val:0; + rout+=(regPool[i*4+2]&0x80)?val:0; + } + // PCM + // simple one-channel sample player, actual hardware is essentially a DAC + // with buffering + if (chan[16].pcm.sample>=0) { + chan[16].accum+=regPool[65]; + if (chan[16].accum>=128) { + DivSample* s=parent->getSample(chan[16].pcm.sample); + if (s->samples>0) { + // TODO stereo samples once DivSample has a support for it + switch (s->depth) { + case 8: + chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data8[chan[16].pcm.pos]*256):0; + chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data8[chan[16].pcm.pos]*256):0; + regPool[64]|=0x20; // for register viewer purposes + break; + case 16: + chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data16[chan[16].pcm.pos]):0; + chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data16[chan[16].pcm.pos]):0; + regPool[64]&=~0x20; + break; + } + } else { + chan[16].pcm.sample=-1; + } + chan[16].accum&=0x7f; + chan[16].pcm.pos++; + if (chan[16].pcm.pos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + chan[16].pcm.pos=s->loopStart; + } else { + chan[16].pcm.sample=-1; + } + } + } + } + int pcmvol=volume_lut_pcm[regPool[64]&0x0f]; + lout+=chan[16].pcm.out_l*pcmvol/64; + rout+=chan[16].pcm.out_r*pcmvol/64; + + bufL[pos]=(short)(lout/2); + bufR[pos]=(short)(rout/2); + } +} + +void DivPlatformVERA::reset() { + for (int i=0; i<17; i++) { + chan[i]=Channel(); + } + memset(regPool,0,66); + for (int i=0; i<16; i++) { + chan[i].vol=63; + rWriteHi(i,2,3); // default pan + } + chan[16].vol=15; +} + +int DivPlatformVERA::calcNoteFreq(int ch, int note) { + if (ch<16) { + return parent->calcBaseFreq(chipClock,2097152,note,false); + } else { + double off=1.0; + if (chan[ch].pcm.sample>=0 && chan[ch].pcm.samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[ch].pcm.sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=s->centerRate/8363.0; + } + } + return (int)(off*parent->calcBaseFreq(chipClock,65536,note,false)); + } +} + +void DivPlatformVERA::tick() { + for (int i=0; i<17; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + if (i<16) { + chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); + rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63)); + } else { + // NB this is currently assuming Amiga instrument type with a 0-64 + // (inclusive) volume range. This envelope is then scaled and added to + // the channel volume. Is this a better way to handle this instead of + // making another identical Amiga instrument type but with a 0-15 + // volume range? + chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); + rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15)); + } + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=calcNoteFreq(0,chan[i].std.arp); + } else { + chan[i].baseFreq=calcNoteFreq(0,chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=calcNoteFreq(0,chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty && i<16) { + rWriteLo(i,3,chan[i].std.duty); + } + if (chan[i].std.hadWave && i<16) { + rWriteHi(i,3,chan[i].std.wave); + } + if (chan[i].freqChanged) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8); + if (i<16) { + if (chan[i].freq>65535) chan[i].freq=65535; + rWrite(i,0,chan[i].freq&0xff); + rWrite(i,1,(chan[i].freq>>8)&0xff); + } else { + if (chan[i].freq>128) chan[i].freq=128; + rWrite(16,1,chan[i].freq&0xff); + } + chan[i].freqChanged=false; + } + } + // PCM + chan[16].std.next(); + if (chan[16].std.hadVol) { + } + if (chan[16].std.hadArp) { + if (!chan[16].inPorta) { + if (chan[16].std.arpMode) { + chan[16].baseFreq=calcNoteFreq(16,chan[16].std.arp); + } else { + chan[16].baseFreq=calcNoteFreq(16,chan[16].note+chan[16].std.arp); + } + } + chan[16].freqChanged=true; + } else { + if (chan[16].std.arpMode && chan[16].std.finishedArp) { + chan[16].baseFreq=calcNoteFreq(16,chan[16].note); + chan[16].freqChanged=true; + } + } +} + +int DivPlatformVERA::dispatch(DivCommand c) { + int tmp; + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + tmp = isMuted[c.chan]?0:chan[c.chan].vol; + if(c.chan<16) { + rWriteLo(c.chan,2,tmp) + } else { + chan[c.chan].pcm.sample=parent->getIns(chan[16].ins)->amiga.initSample; + if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { + chan[c.chan].pcm.sample=-1; + } + chan[16].pcm.pos=0; + rWriteFIFOVol(tmp); + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + break; + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + if(c.chan<16) { + rWriteLo(c.chan,2,0) + } else { + chan[16].pcm.sample=-1; + rWriteFIFOVol(0); + rWrite(16,1,0); + } + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + chan[c.chan].ins=(unsigned char)c.value; + break; + case DIV_CMD_VOLUME: + if (c.chan<16) { + tmp=c.value&0x3f; + chan[c.chan].vol=tmp; + rWriteLo(c.chan,2,(isMuted[c.chan]?0:tmp)); + } else { + tmp=c.value&0x0f; + chan[c.chan].vol=tmp; + rWriteFIFOVol(isMuted[c.chan]?0:tmp); + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=calcNoteFreq(c.chan,c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + 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; + break; + case DIV_CMD_STD_NOISE_MODE: + if (c.chan<16) rWriteLo(c.chan,3,c.value); + break; + case DIV_CMD_WAVE: + if (c.chan<16) rWriteHi(c.chan,3,c.value); + break; + case DIV_CMD_PANNING: { + tmp=0; + tmp|=(c.value&0x10)?1:0; + tmp|=(c.value&0x01)?2:0; + if (c.chan<16) { + rWriteHi(c.chan,2,tmp); + } else { + chan[c.chan].pcm.pan = tmp&3; + } + break; + } + case DIV_CMD_GET_VOLMAX: + if(c.chan<16) { + return 63; + } else { + return 15; + } + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + default: + break; + } + return 1; +} + +void* DivPlatformVERA::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformVERA::getRegisterPool() { + return regPool; +} + +int DivPlatformVERA::getRegisterPoolSize() { + return 66; +} + +void DivPlatformVERA::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +bool DivPlatformVERA::isStereo() { + return true; +} + +void DivPlatformVERA::notifyInsDeletion(void* ins) { + for (int i=0; i<2; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformVERA::poke(unsigned int addr, unsigned short val) { + regPool[addr] = (unsigned char)val; +} + +void DivPlatformVERA::poke(std::vector& wlist) { + for (auto &i: wlist) regPool[i.addr] = (unsigned char)i.val; +} + +int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + for (int i=0; i<17; i++) { + isMuted[i]=false; + } + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + chipClock=25000000; + rate=chipClock/512; + reset(); + return 17; +} + +DivPlatformVERA::~DivPlatformVERA() { +} diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h new file mode 100644 index 000000000..c6a208b90 --- /dev/null +++ b/src/engine/platform/vera.h @@ -0,0 +1,74 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _VERA_H +#define _VERA_H +#include "../dispatch.h" +#include "../instrument.h" +#include "../macroInt.h" + +class DivPlatformVERA: public DivDispatch { + protected: + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins; + bool active, freqChanged, inPorta; + int vol, outVol; + unsigned accum; + int noiseval; + DivMacroInt std; + + struct PCMChannel { + int sample; + int out_l, out_r; + unsigned pos; + unsigned len; + unsigned char freq, pan; + PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0), pan(3) {} + } pcm; + Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {} + }; + Channel chan[17]; + bool isMuted[17]; + unsigned char regPool[66]; + + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void tick(); + void muteChannel(int ch, bool mute); + void notifyInsDeletion(void* ins); + bool isStereo(); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + ~DivPlatformVERA(); + + private: + int calcNoteFreq(int ch, int note); +}; +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 23cb54e1c..e1a7e3a4c 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -258,6 +258,18 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe break; } break; + case DIV_SYSTEM_VERA: + switch (effect) { + case 0x20: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x22: // duty + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + default: + return false; + } + break; default: return false; } diff --git a/src/engine/song.h b/src/engine/song.h index fff1065fe..133175c0a 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -90,8 +90,9 @@ enum DivSystem { DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_LYNX, DIV_SYSTEM_QSOUND, + DIV_SYSTEM_VERA, DIV_SYSTEM_YM2610B_EXT, - DIV_SYSTEM_SEGAPCM_COMPAT + DIV_SYSTEM_SEGAPCM_COMPAT, }; struct DivSong { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3c988ceb8..385c1609b 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -135,6 +135,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_LYNX; case 0xa9: return DIV_SYSTEM_SEGAPCM_COMPAT; + case 0xaa: + return DIV_SYSTEM_VERA; case 0xde: return DIV_SYSTEM_YM2610B_EXT; case 0xe0: @@ -258,6 +260,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0xa8; case DIV_SYSTEM_SEGAPCM_COMPAT: return 0xa9; + case DIV_SYSTEM_VERA: + return 0xaa; case DIV_SYSTEM_YM2610B_EXT: return 0xde; case DIV_SYSTEM_QSOUND: @@ -383,6 +387,8 @@ int DivEngine::getChannelCount(DivSystem sys) { case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_QSOUND: return 19; + case DIV_SYSTEM_VERA: + return 17; } return 0; } @@ -522,6 +528,10 @@ const char* DivEngine::getSongSystemName() { if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910) { return "Bally Midway MCR"; } + + if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_VERA) { + return "Commander X16"; + } break; case 3: break; @@ -650,6 +660,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "Taito Arcade Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom QSound"; + case DIV_SYSTEM_VERA: + return "VERA"; } return "Unknown"; } @@ -775,6 +787,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "Yamaha YM2610B Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom DL-1425"; + case DIV_SYSTEM_VERA: + return "VERA"; } return "Unknown"; } @@ -858,7 +872,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) { } const char* chanNames[38][24]={ - {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM/VERA {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3) {"Square 1", "Square 2", "Square 3", "Noise"}, // SMS @@ -899,7 +913,7 @@ const char* chanNames[38][24]={ }; const char* chanShortNames[38][24]={ - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759 + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759/VERA {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3) {"S1", "S2", "S3", "NO"}, // SMS @@ -939,7 +953,7 @@ const char* chanShortNames[38][24]={ {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) }; -const int chanTypes[38][24]={ +const int chanTypes[39][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759 {0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3) @@ -978,9 +992,10 @@ const int chanTypes[38][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums {3, 3, 3, 3}, //Lynx {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA }; -const DivInstrumentType chanPrefType[44][24]={ +const DivInstrumentType chanPrefType[45][24]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759 {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3) @@ -1025,6 +1040,7 @@ const DivInstrumentType chanPrefType[44][24]={ {DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z {DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3) + {DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, // VERA }; const char* DivEngine::getChannelName(int chan) { @@ -1162,6 +1178,9 @@ const char* DivEngine::getChannelName(int chan) { case DIV_SYSTEM_QSOUND: return chanNames[36][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_VERA: + return chanNames[0][dispatchChanOfChan[chan]]; + break; } return "??"; } @@ -1301,6 +1320,9 @@ const char* DivEngine::getChannelShortName(int chan) { case DIV_SYSTEM_QSOUND: return chanShortNames[36][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_VERA: + return chanShortNames[0][dispatchChanOfChan[chan]]; + break; } return "??"; } @@ -1438,6 +1460,9 @@ int DivEngine::getChannelType(int chan) { case DIV_SYSTEM_LYNX: return chanTypes[36][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_VERA: + return chanTypes[38][dispatchChanOfChan[chan]]; + break; } return 1; } @@ -1588,6 +1613,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { case DIV_SYSTEM_LYNX: return chanPrefType[42][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_VERA: + return chanPrefType[44][dispatchChanOfChan[chan]]; + break; } return DIV_INS_FM; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 58e95003f..f4fd48a28 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4597,6 +4597,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_AY8930); sysAddOption(DIV_SYSTEM_LYNX); sysAddOption(DIV_SYSTEM_QSOUND); + sysAddOption(DIV_SYSTEM_VERA); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -4715,7 +4716,7 @@ bool FurnaceGUI::loop() { break; } case DIV_SYSTEM_YM2151: - if (ImGui::RadioButton("NTSC (3.58MHz)",flags==0)) { + if (ImGui::RadioButton("NTSC/X16 (3.58MHz)",flags==0)) { e->setSysFlags(i,0,restart); updateWindowTitle(); } @@ -4913,6 +4914,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_AY8930); sysChangeOption(i,DIV_SYSTEM_LYNX); sysChangeOption(i,DIV_SYSTEM_QSOUND); + sysChangeOption(i,DIV_SYSTEM_VERA); ImGui::EndMenu(); } } diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 4afa67a5c..1650fd151 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,7 +27,7 @@ #include #include "plot_nolerp.h" -const char* insTypes[24]={ +const char* insTypes[25]={ "Standard", "FM (4-operator)", "Game Boy", @@ -51,7 +51,8 @@ const char* insTypes[24]={ "POKEY", "PC Beeper", "WonderSwan", - "Atari Lynx" + "Atari Lynx", + "VERA" }; const char* ssgEnvTypes[8]={ @@ -783,9 +784,9 @@ void FurnaceGUI::drawInsEdit() { } else { DivInstrument* ins=e->song.ins[curIns]; ImGui::InputText("Name",&ins->name); - if (ins->type<0 || ins->type>23) ins->type=DIV_INS_FM; + if (ins->type<0 || ins->type>24) ins->type=DIV_INS_FM; int insType=ins->type; - if (ImGui::Combo("Type",&insType,insTypes,24,24)) { + if (ImGui::Combo("Type",&insType,insTypes,25,24)) { ins->type=(DivInstrumentType)insType; } @@ -1304,7 +1305,7 @@ void FurnaceGUI::drawInsEdit() { float loopIndicator[256]; const char* volumeLabel="Volume"; - int volMax=(ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)?31:15; + int volMax=15; int volMin=0; if (ins->type==DIV_INS_C64) { if (ins->c64.volIsCutoff) { @@ -1317,6 +1318,12 @@ void FurnaceGUI::drawInsEdit() { } } } + if ((ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)) { + volMax=31; + } + if (ins->type==DIV_INS_VERA) { + volMax=63; + } if (ins->type==DIV_INS_AMIGA) { volMax=64; } @@ -1330,7 +1337,7 @@ void FurnaceGUI::drawInsEdit() { bool arpMode=ins->std.arpMacroMode; const char* dutyLabel="Duty/Noise"; - int dutyMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?31:3; + int dutyMax=3; if (ins->type==DIV_INS_C64) { dutyLabel="Duty"; if (ins->c64.dutyIsAbs) { @@ -1342,6 +1349,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_FM) { dutyMax=32; } + if ((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)) { + dutyMax=31; + } if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_FM) { dutyLabel="Noise Freq"; } @@ -1361,9 +1371,13 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { dutyMax=0; } + if (ins->type==DIV_INS_VERA) { + dutyLabel="Duty"; + dutyMax=63; + } bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs); - int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?3:63; + int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63; bool bitMode=false; if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { bitMode=true; From d209a45b92f93b3b6fb6b9701617e517d6ffd1c5 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sat, 5 Mar 2022 03:11:11 +0700 Subject: [PATCH 006/105] Change sound chip ID to 0xac --- src/engine/sysDef.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 385c1609b..ee771115e 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -135,7 +135,7 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_LYNX; case 0xa9: return DIV_SYSTEM_SEGAPCM_COMPAT; - case 0xaa: + case 0xac: return DIV_SYSTEM_VERA; case 0xde: return DIV_SYSTEM_YM2610B_EXT; @@ -261,7 +261,7 @@ unsigned char DivEngine::systemToFile(DivSystem val) { case DIV_SYSTEM_SEGAPCM_COMPAT: return 0xa9; case DIV_SYSTEM_VERA: - return 0xaa; + return 0xac; case DIV_SYSTEM_YM2610B_EXT: return 0xde; case DIV_SYSTEM_QSOUND: From 7935f527372fe526f18fff84a04aa8f58abf7db1 Mon Sep 17 00:00:00 2001 From: nicco1690 <78063037+nicco1690@users.noreply.github.com> Date: Sat, 5 Mar 2022 23:27:48 -0500 Subject: [PATCH 007/105] Create Lynx MIKEY sound docs --- papers/doc/7-systems/lynx.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 papers/doc/7-systems/lynx.md diff --git a/papers/doc/7-systems/lynx.md b/papers/doc/7-systems/lynx.md new file mode 100644 index 000000000..c9c9bc26d --- /dev/null +++ b/papers/doc/7-systems/lynx.md @@ -0,0 +1,12 @@ +# Atari Lynx/MIKEY + +The Atari Lynx is a 16 bit handheld console developed by (obviously) Atari Corporation, and initially released in September of 1989, with the worldwide release being in 1990. + +The Atari Lynx's custom sound chip and CPU (MIKEY) is a 6502-based 8 bit CPU running at 16MHz, however this information is generally not useful in the context of Furnace. + +## Sound capabilities + + - The MIKEY has 4 channels of square wave-based sound, which can be modulated with different frequencies (×0, ×1, ×2, ×3, ×4, ×5, ×7, ×10, and ×11) to create wavetable-like results. + - The MIKEY also has hard stereo panning capabilities via the `08xx` effect command. + - The MIKEY has four 8-bit DACs (Digital to Analog Converter) — one for each voice — that essentially mean you can play samples on the MIKEY (at the cost of CPU time and memory). + - The MIKEY also has a variety of pitches to choose from, and they go from 32Hz to "above the range of human hearing", according to Atari. From 4a83c7c5a71f125b9c5cd5d450e5a2a822351f22 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 02:31:03 +0900 Subject: [PATCH 008/105] Add Seta/Allumer X1-010 Support its 16 channel wavetable/PCM chip, with (optional) stereo support. Its also has envelope, this feature has similar as AY PSG's one but its shape is also stored at RAM, and each nibble in envelope data is for each output: so i decided to added some feature for more stereo-ish envelope. Split: Envelope shape will be splitted to Left and Right half for each output. HInv, Vinv: Envelope shape will be Horizontally/Vertically mirrored the left one. Max sample length is sample bank size of Seta 2 arcade hardware (currently not emulated yet, nor it doesn't support on VGM). Chip id is temporary, it can be changed with to suggestions. --- .gitmodules | 3 + CMakeLists.txt | 6 +- extern/cam900_vgsound_emu | 1 + papers/doc/4-instrument/README.md | 3 +- papers/doc/4-instrument/x1_010.md | 11 + papers/doc/6-sample/README.md | 3 +- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/x1_010.md | 34 ++ papers/format.md | 1 + src/engine/dispatch.h | 7 + src/engine/dispatchContainer.cpp | 6 +- src/engine/engine.cpp | 34 +- src/engine/engine.h | 2 + src/engine/instrument.h | 1 + src/engine/platform/x1_010.cpp | 862 ++++++++++++++++++++++++++++++ src/engine/platform/x1_010.h | 138 +++++ src/engine/playback.cpp | 36 ++ src/engine/sample.cpp | 5 +- src/engine/sample.h | 2 +- src/engine/song.h | 10 +- src/engine/sysDef.cpp | 38 +- src/engine/vgmOps.cpp | 36 ++ src/gui/debug.cpp | 40 ++ src/gui/gui.cpp | 56 +- src/gui/gui.h | 1 + src/gui/insEdit.cpp | 26 +- src/gui/settings.cpp | 2 + 27 files changed, 1340 insertions(+), 25 deletions(-) create mode 160000 extern/cam900_vgsound_emu create mode 100644 papers/doc/4-instrument/x1_010.md create mode 100644 papers/doc/7-systems/x1_010.md create mode 100644 src/engine/platform/x1_010.cpp create mode 100644 src/engine/platform/x1_010.h diff --git a/.gitmodules b/.gitmodules index 435f43c75..56f11c097 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "extern/adpcm"] path = extern/adpcm url = https://github.com/superctr/adpcm +[submodule "extern/cam900_vgsound_emu"] + path = extern/cam900_vgsound_emu + url = https://github.com/cam900/vgsound_emu diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bcb8ce98..53aebac00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,9 @@ extern/adpcm/ymz_codec.c extern/Nuked-OPN2/ym3438.c extern/opm/opm.c extern/Nuked-OPLL/opll.c + +extern/cam900_vgsound_emu/x1_010/x1_010.cpp + src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp @@ -304,8 +307,9 @@ src/engine/platform/amiga.cpp src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp -src/engine/platform/dummy.cpp +src/engine/platform/x1_010.cpp src/engine/platform/lynx.cpp +src/engine/platform/dummy.cpp ) if (WIN32) diff --git a/extern/cam900_vgsound_emu b/extern/cam900_vgsound_emu new file mode 160000 index 000000000..cf8816851 --- /dev/null +++ b/extern/cam900_vgsound_emu @@ -0,0 +1 @@ +Subproject commit cf88168512c1b32bbea7b7dcc1411c4da429a723 diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index b9d4b8f40..f6da9d26c 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -21,8 +21,9 @@ depending on the instrument type, there are currently 10 different types of an i - [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source. - [TIA](tia.md) - for use with Atari 2600 system. - [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610. -- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM and PC Engine's sample playback mode. +- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM, X1-010 and PC Engine's sample playback mode. - [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console. +- [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010. # macros diff --git a/papers/doc/4-instrument/x1_010.md b/papers/doc/4-instrument/x1_010.md new file mode 100644 index 000000000..8f3691ee6 --- /dev/null +++ b/papers/doc/4-instrument/x1_010.md @@ -0,0 +1,11 @@ +# X1-010 instrument editor + +X1-010 instrument editor consists of 7 macros. + +- [Volume] - volume levels sequence +- [Arpeggio]- pitch sequence +- [Waveform] - spicifies wavetables sequence +- [Envelope Mode] - allows shaping an envelope +- [Envelope] - spicifies envelope shape sequence, it's also wavetable. +- [Auto envelope numerator] - sets the envelope to the channel's frequency multiplied by numerator +- [Auto envelope denominator] - ets the envelope to the channel's frequency multiplied by denominator diff --git a/papers/doc/6-sample/README.md b/papers/doc/6-sample/README.md index 78eb322c3..87ddf0f8f 100644 --- a/papers/doc/6-sample/README.md +++ b/papers/doc/6-sample/README.md @@ -12,7 +12,8 @@ As of Furnace 0.5.5, the following sound chips have sample support: - PC Engine/TurboGrafx 16/Huc6280 (same conditions as above) - Amiga/Paula (on all channels AND resamplable, but you need to make an instrument with the Amiga format and tie it to a sample first) - Arcade/SEGA PCM (same as above but you don't need to make an instrument for it and you have to use the `20xx` effect command to resample your samples) - - Neo Geo/Neo Geo EXT-Ch2 (on the last 5 channels only and can be resampled the same way as above) + - Neo Geo/Neo Geo EXT-Ch2 (on the last 7 channels only and can be resampled the same way as above) + - Seta/Allumer X1-010 (same as above, and both `1701` and `20xx` effect commands are affected on all 16 channels) Furnace also has a feature where you can make an Amiga formarted instrument on the YM2612 and Huc6280 to resample a sample you have in the module. diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 78aeb8cea..1a0d28096 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -18,5 +18,6 @@ this is a list of systems that Furnace supports, including each system's effects - [Atari 2600](tia.md) - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) +- [Seta/Allumer X1-010](x1_010.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all. diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md new file mode 100644 index 000000000..952e42316 --- /dev/null +++ b/papers/doc/7-systems/x1_010.md @@ -0,0 +1,34 @@ +# Seta/Allumer X1-010 + +One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-88 to early-2000s. +it has 2 output channel, but no known hardware using this feature for stereo sound. +later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision. +Allumer one is just rebadged Seta's thing for use in their arcade hardwares. + +It has 16 channels, and all channels can be switchable to PCM sample or wavetable playback mode. +Wavetable needs to paired with envelope, this feature is similar as AY PSG but it's shape are stored at RAM: it means it is user-definable. + +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. + +# effects + +- `10xx`: change wave. +- `11xx`: change envelope shape. (also wavetable) +- `17xx`: toggle PCM mode. +- `20xx`: set PCM frequency.* +- `22xx`: set envelope mode. + - 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. +- `23xx`: set envelope period. +- `25xx`: slide envelope period up. +- `26xx`: slide envelope period down. +- `29xy`: enable auto-envelope mode. + - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. + - `x` is the numerator. + - `y` is the denominator. + - if `x` or `y` are 0 this will disable auto-envelope mode. + +* PCM frequency: 255 step, fomula: `step * (Chip clock / 8192)`; 1.95KHz to 498KHz if Chip clock is 16MHz. diff --git a/papers/format.md b/papers/format.md index 4df49ed02..ff068d830 100644 --- a/papers/format.md +++ b/papers/format.md @@ -174,6 +174,7 @@ size | description | - 0xaa: MSM6295 - 4 channels | - 0xab: MSM6258 - 1 channel | - 0xac: Commander X16 (VERA) - 17 channels + | - 0xb0: Seta/Allumer X1-010 - 16 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 300b0033b..7e1a56081 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -103,6 +103,13 @@ enum DivDispatchCmds { DIV_CMD_QSOUND_ECHO_DELAY, DIV_CMD_QSOUND_ECHO_LEVEL, + DIV_CMD_X1_010_ENVELOPE_SHAPE, + DIV_CMD_X1_010_ENVELOPE_ENABLE, + DIV_CMD_X1_010_ENVELOPE_MODE, + DIV_CMD_X1_010_ENVELOPE_PERIOD, + DIV_CMD_X1_010_ENVELOPE_SLIDE, + DIV_CMD_X1_010_AUTO_ENVELOPE, + DIV_ALWAYS_SET_VOLUME, DIV_CMD_MAX diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 0682bbd71..84b8315f3 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -40,8 +40,9 @@ #include "platform/pcspkr.h" #include "platform/segapcm.h" #include "platform/qsound.h" -#include "platform/dummy.h" +#include "platform/x1_010.h" #include "platform/lynx.h" +#include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -230,6 +231,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SEGAPCM_COMPAT: dispatch=new DivPlatformSegaPCM; break; + case DIV_SYSTEM_X1_010: + dispatch=new DivPlatformX1_010; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a8b023d7d..e484baf31 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -532,6 +532,36 @@ void DivEngine::renderSamples() { memPos+=length+16; } qsoundMemLen=memPos+256; + + // step 4: allocate x1-010 pcm samples + if (x1_010Mem==NULL) x1_010Mem=new unsigned char[1048576]; + memset(x1_010Mem,0,1048576); + + memPos=0; + for (int i=0; ilength8+4095)&(~0xfff); + // fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!) + if (paddedLen>131072) { + paddedLen=131072; + } + if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) { + memPos=(memPos+0x1ffff)&0xfe0000; + } + if (memPos>=1048576) { + logW("out of X1-010 memory for sample %d!\n",i); + break; + } + if (memPos+paddedLen>=1048576) { + memcpy(x1_010Mem+memPos,s->data8,1048576-memPos); + logW("out of X1-010 memory for sample %d!\n",i); + } else { + memcpy(x1_010Mem+memPos,s->data8,paddedLen); + } + s->offX1_010=memPos; + memPos+=paddedLen; + } + x1_010MemLen=memPos+256; } void DivEngine::createNew(const int* description) { @@ -950,7 +980,9 @@ int DivEngine::getEffectiveSampleRate(int rate) { case DIV_SYSTEM_QSOUND: return (24038*MIN(65535,(rate*4096/24038)))/4096; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: - return 18518; + return 18518; // TODO: support ADPCM-B case + case DIV_SYSTEM_X1_010: + return (31250*MIN(255,(rate*16/31250)))/16; // TODO: support variable clock case default: break; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 19d589dba..d16a987cb 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -632,6 +632,8 @@ class DivEngine { size_t qsoundAMemLen; unsigned char* dpcmMem; size_t dpcmMemLen; + unsigned char* x1_010Mem; + size_t x1_010MemLen; DivEngine(): output(NULL), diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 27ce2a4ce..da7356f1e 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -48,6 +48,7 @@ enum DivInstrumentType { DIV_INS_BEEPER=21, DIV_INS_SWAN=22, DIV_INS_MIKEY=23, + DIV_INS_X1_010=24, }; // FM operator structure: diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp new file mode 100644 index 000000000..6d072d730 --- /dev/null +++ b/src/engine/platform/x1_010.cpp @@ -0,0 +1,862 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "x1_010.h" +#include "../engine.h" +#include + +//#define rWrite(a,v) pendingWrites[a]=v; +#define rWrite(a,v) if (!skipRegisterWrites) { x1_010->ram_w(a,v); if (dumpWrites) { addWrite(a,v); } } + +#define chRead(c,a) x1_010->ram_r((c<<3)|(a&7)) +#define chWrite(c,a,v) rWrite((c<<3)|(a&7),v) +#define waveWrite(c,a,v) rWrite(0x1000|(chan[c].waveBank<<11)|(c<<7)|(a&0x7f),(v-128)&0xff) +#define envFill(c,a) rWrite(0x800|(c<<7)|(a&0x7f),(chan[c].lvol<<4)|chan[c].rvol) +#define envWrite(c,a,l,r) rWrite(0x800|(c<<7)|(a&0x7f),(((chan[c].lvol*(l))/15)<<4)|((chan[c].rvol*(r))/15)) + +#define refreshControl(c) chWrite(c,0,chan[c].active?(chan[c].pcm?1:((chan[c].env.flag.envEnable&&chan[c].env.flag.envOneshot)?7:3)):0); + +#define CHIP_FREQBASE 4194304 + +const char* regCheatSheetX1_010[]={ + // Channel registers + "Ch00_Control", "0000", + "Ch00_PCMVol_WavSel", "0001", + "Ch00_FreqL", "0002", + "Ch00_FreqH", "0003", + "Ch00_Start_EnvFrq", "0004", + "Ch00_End_EnvSel", "0005", + "Ch01_Control", "0008", + "Ch01_PCMVol_WavSel", "0009", + "Ch01_FreqL", "000A", + "Ch01_FreqH", "000B", + "Ch01_Start_EnvFrq", "000C", + "Ch01_End_EnvSel", "000D", + "Ch02_Control", "0010", + "Ch02_PCMVol_WavSel", "0011", + "Ch02_FreqL", "0012", + "Ch02_FreqH", "0013", + "Ch02_Start_EnvFrq", "0014", + "Ch02_End_EnvSel", "0015", + "Ch03_Control", "0018", + "Ch03_PCMVol_WavSel", "0019", + "Ch03_FreqL", "001A", + "Ch03_FreqH", "001B", + "Ch03_Start_EnvFrq", "001C", + "Ch03_End_EnvSel", "001D", + "Ch04_Control", "0020", + "Ch04_PCMVol_WavSel", "0021", + "Ch04_FreqL", "0022", + "Ch04_FreqH", "0023", + "Ch04_Start_EnvFrq", "0024", + "Ch04_End_EnvSel", "0025", + "Ch05_Control", "0028", + "Ch05_PCMVol_WavSel", "0029", + "Ch05_FreqL", "002A", + "Ch05_FreqH", "002B", + "Ch05_Start_EnvFrq", "002C", + "Ch05_End_EnvSel", "002D", + "Ch06_Control", "0030", + "Ch06_PCMVol_WavSel", "0031", + "Ch06_FreqL", "0032", + "Ch06_FreqH", "0033", + "Ch06_Start_EnvFrq", "0034", + "Ch06_End_EnvSel", "0035", + "Ch07_Control", "0038", + "Ch07_PCMVol_WavSel", "0039", + "Ch07_FreqL", "003A", + "Ch07_FreqH", "003B", + "Ch07_Start_EnvFrq", "003C", + "Ch07_End_EnvSel", "003D", + "Ch08_Control", "0040", + "Ch08_PCMVol_WavSel", "0041", + "Ch08_FreqL", "0042", + "Ch08_FreqH", "0043", + "Ch08_Start_EnvFrq", "0044", + "Ch08_End_EnvSel", "0045", + "Ch09_Control", "0048", + "Ch09_PCMVol_WavSel", "0049", + "Ch09_FreqL", "004A", + "Ch09_FreqH", "004B", + "Ch09_Start_EnvFrq", "004C", + "Ch09_End_EnvSel", "004D", + "Ch10_Control", "0050", + "Ch10_PCMVol_WavSel", "0051", + "Ch10_FreqL", "0052", + "Ch10_FreqH", "0053", + "Ch10_Start_EnvFrq", "0054", + "Ch10_End_EnvSel", "0055", + "Ch11_Control", "0058", + "Ch11_PCMVol_WavSel", "0059", + "Ch11_FreqL", "005A", + "Ch11_FreqH", "005B", + "Ch11_Start_EnvFrq", "005C", + "Ch11_End_EnvSel", "005D", + "Ch12_Control", "0060", + "Ch12_PCMVol_WavSel", "0061", + "Ch12_FreqL", "0062", + "Ch12_FreqH", "0063", + "Ch12_Start_EnvFrq", "0064", + "Ch12_End_EnvSel", "0065", + "Ch13_Control", "0068", + "Ch13_PCMVol_WavSel", "0069", + "Ch13_FreqL", "006A", + "Ch13_FreqH", "006B", + "Ch13_Start_EnvFrq", "006C", + "Ch13_End_EnvSel", "006D", + "Ch14_Control", "0070", + "Ch14_PCMVol_WavSel", "0071", + "Ch14_FreqL", "0072", + "Ch14_FreqH", "0073", + "Ch14_Start_EnvFrq", "0074", + "Ch14_End_EnvSel", "0075", + "Ch15_Control", "0078", + "Ch15_PCMVol_WavSel", "0079", + "Ch15_FreqL", "007A", + "Ch15_FreqH", "007B", + "Ch15_Start_EnvFrq", "007C", + "Ch15_End_EnvSel", "007D", + // Envelope data + "Env01Data", "0080", + "Env02Data", "0100", + "Env03Data", "0180", + "Env04Data", "0200", + "Env05Data", "0280", + "Env06Data", "0300", + "Env07Data", "0380", + "Env08Data", "0400", + "Env09Data", "0480", + "Env10Data", "0500", + "Env11Data", "0580", + "Env12Data", "0600", + "Env13Data", "0680", + "Env14Data", "0700", + "Env15Data", "0780", + "Env16Data", "0800", + "Env17Data", "0880", + "Env18Data", "0900", + "Env19Data", "0980", + "Env20Data", "0A00", + "Env21Data", "0A80", + "Env22Data", "0B00", + "Env23Data", "0B80", + "Env24Data", "0C00", + "Env25Data", "0C80", + "Env26Data", "0D00", + "Env27Data", "0D80", + "Env28Data", "0E00", + "Env29Data", "0E80", + "Env30Data", "0F00", + "Env31Data", "0F80", + // Wavetable data + "Wave00Data", "1000", + "Wave01Data", "1080", + "Wave02Data", "1100", + "Wave03Data", "1180", + "Wave04Data", "1200", + "Wave05Data", "1280", + "Wave06Data", "1300", + "Wave07Data", "1380", + "Wave08Data", "1400", + "Wave09Data", "1480", + "Wave10Data", "1500", + "Wave11Data", "1580", + "Wave12Data", "1600", + "Wave13Data", "1680", + "Wave14Data", "1700", + "Wave15Data", "1780", + "Wave16Data", "1800", + "Wave17Data", "1880", + "Wave18Data", "1900", + "Wave19Data", "1980", + "Wave20Data", "1A00", + "Wave21Data", "1A80", + "Wave22Data", "1B00", + "Wave23Data", "1B80", + "Wave24Data", "1C00", + "Wave25Data", "1C80", + "Wave26Data", "1D00", + "Wave27Data", "1D80", + "Wave28Data", "1E00", + "Wave29Data", "1E80", + "Wave30Data", "1F00", + "Wave31Data", "1F80", + NULL +}; + +const char** DivPlatformX1_010::getRegisterSheet() { + return regCheatSheetX1_010; +} + +const char* DivPlatformX1_010::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + case 0x11: + return "11xx: Change envelope shape"; + break; + case 0x17: + return "17xx: Toggle PCM mode"; + break; + case 0x20: + return "20xx: Set PCM frequency"; + 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)"; + break; + case 0x23: + return "23xx: Set envelope period"; + break; + case 0x25: + return "25xx: Envelope slide up"; + break; + case 0x26: + return "26xx: Envelope slide down"; + break; + case 0x29: + return "29xy: Set auto-envelope (x: numerator; y: denominator)"; + break; + } + return NULL; +} + +void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; htick(); + + signed short tempL=x1_010->output(0); + signed short tempR=x1_010->output(1); + + if (tempL<-32768) tempL=-32768; + if (tempL>32767) tempL=32767; + if (tempR<-32768) tempR=-32768; + if (tempR>32767) tempR=32767; + + //printf("tempL: %d tempR: %d\n",tempL,tempR); + bufL[h]=stereo?tempL:((tempL+tempR)>>1); + bufR[h]=stereo?tempR:bufL[h]; + } +} + +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) { + DivSample* s=parent->getSample(sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=s->centerRate/8363.0; + } + } + return off*parent->calcBaseFreq(chipClock,8192,note,false); + } + // Wavetable note + return NOTE_FREQUENCY(note); +} + +void DivPlatformX1_010::updateWave(int ch) { + DivWavetable* wt=parent->getWave(chan[ch].wave); + if (chan[ch].active) { + chan[ch].waveBank ^= 1; + } + for (int i=0; i<128; i++) { + if (wt->max<1 || wt->len<1) { + waveWrite(ch,i,0); + } else { + waveWrite(ch,i,wt->data[i*wt->len/128]*255/wt->max); + } + } + if (!chan[ch].pcm) { + chWrite(ch,1,(chan[ch].waveBank<<4)|(ch&0xf)); + } +} + +void DivPlatformX1_010::updateEnvelope(int ch) { + if (!chan[ch].pcm) { + if (isMuted[ch]) { + for (int i=0; i<128; i++) { + rWrite(0x800|(ch<<7)|(i&0x7f),0); + } + } else { + if (!chan[ch].env.flag.envEnable) { + for (int i=0; i<128; i++) { + envFill(ch,i); + } + } else { + DivWavetable* wt=parent->getWave(chan[ch].env.shape); + for (int i=0; i<128; i++) { + if (wt->max<1 || wt->len<1) { + envFill(ch,i); + } else if (stereo&&(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 + 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; + } else { + 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 + envWrite(ch,i,lo,ro); + } else { + int out = wt->data[i*wt->len/128]*15/wt->max; + envWrite(ch,i,out,out); + } + } + } + } + chWrite(ch,5,0x10|(ch&0xf)); + } else { + chWrite(ch,1,(chan[ch].lvol<<4)|chan[ch].rvol); + } +} + +void DivPlatformX1_010::tick() { + for (int i=0; i<16; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + signed char macroVol=((chan[i].vol&15)*MIN(chan[i].furnacePCM?64:15,chan[i].std.vol))/(chan[i].furnacePCM?64:15); + if ((!isMuted[i])&&(macroVol!=chan[i].outVol)) { + chan[i].outVol=macroVol; + chan[i].envChanged=true; + } + } + if ((!chan[i].pcm) || chan[i].furnacePCM) { + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NoteX1_010(i,chan[i].std.arp); + } else { + chan[i].baseFreq=NoteX1_010(i,chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + 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].wave!=chan[i].std.wave) { + chan[i].wave=chan[i].std.wave; + if (!chan[i].pcm) { + updateWave(i); + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } + } + if (chan[i].std.hadEx1) { + bool nextEnable=(chan[i].std.ex1&1); + if (nextEnable!=(chan[i].env.flag.envEnable)) { + chan[i].env.flag.envEnable=nextEnable; + if (!chan[i].pcm) { + if (!isMuted[i]) { + chan[i].envChanged=true; + } + refreshControl(i); + } + } + bool nextOneshot=(chan[i].std.ex1&2); + if (nextOneshot!=(chan[i].env.flag.envOneshot)) { + chan[i].env.flag.envOneshot=nextOneshot; + if (!chan[i].pcm) { + refreshControl(i); + } + } + if (stereo) { + 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) { + 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) { + 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) { + chan[i].envChanged=true; + } + } + } + } + if (chan[i].std.hadEx2) { + 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])) { + chan[i].envChanged=true; + } + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } + } + if (chan[i].std.hadEx3) { + chan[i].autoEnvNum=chan[i].std.ex3; + if (!chan[i].pcm) { + chan[i].freqChanged=true; + if (!chan[i].std.willAlg) chan[i].autoEnvDen=1; + } + } + if (chan[i].std.hadAlg) { + chan[i].autoEnvDen=chan[i].std.alg; + if (!chan[i].pcm) { + chan[i].freqChanged=true; + if (!chan[i].std.willEx3) chan[i].autoEnvNum=1; + } + } + if (chan[i].envChanged) { + if (!isMuted[i]) { + if (stereo) { + chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; + chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15; + } else { + chan[i].lvol=chan[i].rvol=chan[i].outVol; + } + } + updateEnvelope(i); + chan[i].envChanged=false; + } + 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; + if (chan[i].freq>255) chan[i].freq=255; + chWrite(i,2,chan[i].freq&0xff); + } else { + 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) { + 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)) { + refreshControl(i); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + if (chan[i].env.slide!=0) { + chan[i].env.slidefrac+=chan[i].env.slide; + while (chan[i].env.slidefrac>0xf) { + chan[i].env.slidefrac-=0x10; + if (chan[i].env.period<0xff) { + chan[i].env.period++; + if (!chan[i].pcm) { + chWrite(i,4,chan[i].env.period); + } + } + } + while (chan[i].env.slidefrac<-0xf) { + chan[i].env.slidefrac+=0x10; + if (chan[i].env.period>0) { + chan[i].env.period--; + if (!chan[i].pcm) { + chWrite(i,4,chan[i].env.period); + } + } + } + } + } +} + +int DivPlatformX1_010::dispatch(DivCommand c) { + switch (c.cmd) { + 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].furnacePCM=true; + } else { + chan[c.chan].furnacePCM=false; + } + if (skipRegisterWrites) break; + if (chan[c.chan].furnacePCM) { + 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) { + 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 + chWrite(c.chan,5,(0x100-(end>>12))&0xff); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + } else { + chan[c.chan].std.init(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + chWrite(c.chan,0,0); // reset + chWrite(c.chan,1,0); + chWrite(c.chan,2,0); + chWrite(c.chan,4,0); + chWrite(c.chan,5,0); + break; + } + } + } else { + chan[c.chan].std.init(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + chWrite(c.chan,0,0); // reset + chWrite(c.chan,1,0); + chWrite(c.chan,2,0); + chWrite(c.chan,4,0); + chWrite(c.chan,5,0); + break; + } + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + chWrite(c.chan,4,(s->offX1_010>>12)&0xff); + int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded + chWrite(c.chan,5,(0x100-(end>>12))&0xff); + chan[c.chan].baseFreq=(((unsigned int)s->rate)<<4)/(chipClock/512); + chan[c.chan].freqChanged=true; + } + } else if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].envChanged=true; + chan[c.chan].std.init(ins); + refreshControl(c.chan); + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].pcm=false; + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + if (chan[c.chan].outVol!=c.value) { + chan[c.chan].outVol=c.value; + if (!isMuted[c.chan]) { + chan[c.chan].envChanged=true; + } + } + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.hasVol) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value; + updateWave(c.chan); + chan[c.chan].keyOn=true; + break; + case DIV_CMD_X1_010_ENVELOPE_SHAPE: + if (chan[c.chan].env.shape!=c.value) { + chan[c.chan].env.shape=c.value; + if (!chan[c.chan].pcm) { + if (chan[c.chan].env.flag.envEnable&&(!isMuted[c.chan])) { + chan[c.chan].envChanged=true; + } + chan[c.chan].keyOn=true; + } + } + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NoteX1_010(c.chan,c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_SAMPLE_MODE: + if (chan[c.chan].pcm!=(c.value&1)) { + chan[c.chan].pcm=c.value&1; + chan[c.chan].freqChanged=true; + if (!isMuted[c.chan]) { + chan[c.chan].envChanged=true; + } + } + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_PANNING: { + if (stereo&&(chan[c.chan].pan!=c.value)) { + chan[c.chan].pan=c.value; + if (!isMuted[c.chan]) { + chan[c.chan].envChanged=true; + } + } + break; + } + 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].freqChanged=true; + break; + case DIV_CMD_PRE_PORTA: + 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; + break; + case DIV_CMD_SAMPLE_FREQ: + if (chan[c.chan].pcm) { + chan[c.chan].freq=c.value&0xff; + chWrite(c.chan,2,chan[c.chan].freq&0xff); + if (chRead(c.chan,0)&1) { + refreshControl(c.chan); + } + } + break; + case DIV_CMD_X1_010_ENVELOPE_MODE: { + bool nextEnable=c.value&1; + if (nextEnable!=(chan[c.chan].env.flag.envEnable)) { + chan[c.chan].env.flag.envEnable=nextEnable; + if (!chan[c.chan].pcm) { + if (!isMuted[c.chan]) { + chan[c.chan].envChanged=true; + } + refreshControl(c.chan); + } + } + bool nextOneshot=c.value&2; + if (nextOneshot!=(chan[c.chan].env.flag.envOneshot)) { + chan[c.chan].env.flag.envOneshot=nextOneshot; + if (!chan[c.chan].pcm) { + refreshControl(c.chan); + } + } + if (stereo) { + 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) { + 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) { + 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) { + chan[c.chan].envChanged=true; + } + } + } + break; + } + case DIV_CMD_X1_010_ENVELOPE_PERIOD: + chan[c.chan].env.period=c.value; + if (!chan[c.chan].pcm) { + chWrite(c.chan,4,chan[c.chan].env.period); + } + break; + case DIV_CMD_X1_010_ENVELOPE_SLIDE: + chan[c.chan].env.slide=c.value; + break; + case DIV_CMD_X1_010_AUTO_ENVELOPE: + chan[c.chan].autoEnvNum=c.value>>4; + chan[c.chan].autoEnvDen=c.value&15; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformX1_010::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + chan[ch].envChanged=true; +} + +void DivPlatformX1_010::forceIns() { + for (int i=0; i<16; i++) { + chan[i].insChanged=true; + chan[i].envChanged=true; + chan[i].freqChanged=true; + updateWave(i); + } +} + +void* DivPlatformX1_010::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformX1_010::getRegisterPool() { + for (int i=0; i<0x2000; i++) { + regPool[i]=x1_010->ram_r(i); + } + return regPool; +} + +int DivPlatformX1_010::getRegisterPoolSize() { + return 0x2000; +} + +void DivPlatformX1_010::reset() { + memset(regPool,0,0x2000); + for (int i=0; i<16; i++) { + chan[i]=DivPlatformX1_010::Channel(); + chan[i].reset(); + } + x1_010->reset(); + sampleBank=0; + // set per-channel initial panning + for (int i=0; i<16; i++) { + chWrite(i,0,0); + } +} + +bool DivPlatformX1_010::isStereo() { + return stereo; +} + +bool DivPlatformX1_010::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformX1_010::notifyWaveChange(int wave) { + for (int i=0; i<16; i++) { + if (chan[i].wave==wave) { + updateWave(i); + } + } +} + +void DivPlatformX1_010::notifyInsDeletion(void* ins) { + for (int i=0; i<16; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformX1_010::setFlags(unsigned int flags) { + switch (flags&15) { + case 0: // 16MHz (earlier hardwares) + chipClock=16000000; + break; + case 1: // 16.67MHz (later hardwares) + chipClock=50000000.0/3.0; + break; + // Other clock is used? + } + rate=chipClock/512; + stereo=flags&16; +} + +void DivPlatformX1_010::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformX1_010::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + stereo=false; + for (int i=0; i<16; i++) { + isMuted[i]=false; + } + setFlags(flags); + intf.parent=parent; + x1_010=new x1_010_core(intf); + x1_010->reset(); + reset(); + return 16; +} + +void DivPlatformX1_010::quit() { + delete x1_010; +} + +DivPlatformX1_010::~DivPlatformX1_010() { +} diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h new file mode 100644 index 000000000..27bf4f4a6 --- /dev/null +++ b/src/engine/platform/x1_010.h @@ -0,0 +1,138 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _X1_010_H +#define _X1_010_H + +#include +#include "../dispatch.h" +#include "../engine.h" +#include "../macroInt.h" +#include "../../../extern/cam900_vgsound_emu/x1_010/x1_010.hpp" + +class DivX1_010Interface: public x1_010_intf { + public: + DivEngine* parent; + int sampleBank; + virtual u8 read_rom(uint32_t address) override { + if (parent->x1_010Mem==NULL) return 0; + return parent->x1_010Mem[address & 0xfffff]; + } + DivX1_010Interface(): parent(NULL), sampleBank(0) {} +}; + +class DivPlatformX1_010: public DivDispatch { + struct Channel { + struct Envelope { + struct EnvFlag { + unsigned char envEnable : 1; + unsigned char envOneshot : 1; + unsigned char envSplit : 1; + unsigned char envHinv : 1; + unsigned char envVinv : 1; + void reset() { + envEnable=0; + envOneshot=0; + envSplit=0; + envHinv=0; + envVinv=0; + } + EnvFlag(): + envEnable(0), + envOneshot(0), + envSplit(0), + envHinv(0), + envVinv(0) {} + }; + int shape, period, slide, slidefrac; + EnvFlag flag; + void reset() { + shape=-1; + period=0; + flag.reset(); + } + Envelope(): + shape(-1), + period(0), + slide(0), + slidefrac(0) {} + }; + int freq, baseFreq, pitch, note; + int wave, sample, ins; + unsigned char pan, autoEnvNum, autoEnvDen; + bool active, insChanged, envChanged, freqChanged, keyOn, keyOff, inPorta, furnacePCM, pcm; + int vol, outVol, lvol, rvol; + unsigned char waveBank; + Envelope env; + DivMacroInt std; + void reset() { + freq = baseFreq = pitch = note = 0; + wave = sample = ins = -1; + pan = 255; + autoEnvNum = autoEnvDen = 0; + active = false; + insChanged = envChanged = freqChanged = true; + keyOn = keyOff = inPorta = furnacePCM = pcm = false; + vol = outVol = lvol = rvol = 15; + waveBank = 0; + } + Channel(): + freq(0), baseFreq(0), pitch(0), note(0), + wave(-1), sample(-1), ins(-1), + pan(255), autoEnvNum(0), autoEnvDen(0), + active(false), insChanged(true), envChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), furnacePCM(false), pcm(false), + vol(15), outVol(15), lvol(15), rvol(15), + waveBank(0) {} + }; + Channel chan[16]; + bool isMuted[16]; + bool stereo=false; + unsigned char sampleBank; + DivX1_010Interface intf; + x1_010_core* x1_010; + unsigned char regPool[0x2000]; + double NoteX1_010(int ch, int note); + void updateWave(int ch); + void updateEnvelope(int ch); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void setFlags(unsigned int flags); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformX1_010(); +}; + +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index f0f43dc1a..2fba35336 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -248,6 +248,18 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; + } + case DIV_SYSTEM_X1_010: + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // select envelope shape + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SHAPE,ch,effectVal)); + break; + case 0x17: // PCM enable + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; } break; default: @@ -533,6 +545,30 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char } return false; break; + case DIV_SYSTEM_X1_010: + switch (effect) { + case 0x20: // PCM frequency + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); + break; + case 0x22: // envelope mode + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_MODE,ch,effectVal)); + break; + case 0x23: // envelope period + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_PERIOD,ch,effectVal)); + break; + case 0x25: // envelope slide up + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,effectVal)); + break; + case 0x26: // envelope slide down + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,-effectVal)); + break; + case 0x29: // auto-envelope + dispatchCmd(DivCommand(DIV_CMD_X1_010_AUTO_ENVELOPE,ch,effectVal)); + break; + default: + return false; + } + break; default: return false; } diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index ec6313fff..b802bf791 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -115,8 +115,9 @@ bool DivSample::initInternal(unsigned char d, int count) { case 8: // 8-bit if (data8!=NULL) delete[] data8; length8=count; - data8=new signed char[length8]; - memset(data8,0,length8); + // for padding X1-010 sample + data8=new signed char[(count+4095)&(~0xfff)]; + memset(data8,0,(count+4095)&(~0xfff)); break; case 9: // BRR if (dataBRR!=NULL) delete[] dataBRR; diff --git a/src/engine/sample.h b/src/engine/sample.h index 2915f2cef..8b6d16b19 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -49,7 +49,7 @@ struct DivSample { unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX; - unsigned int offSegaPCM, offQSound; + unsigned int offSegaPCM, offQSound, offX1_010; unsigned int samples; diff --git a/src/engine/song.h b/src/engine/song.h index d1e255d95..34d77386a 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -91,7 +91,8 @@ enum DivSystem { DIV_SYSTEM_LYNX, DIV_SYSTEM_QSOUND, DIV_SYSTEM_YM2610B_EXT, - DIV_SYSTEM_SEGAPCM_COMPAT + DIV_SYSTEM_SEGAPCM_COMPAT, + DIV_SYSTEM_X1_010 }; struct DivSong { @@ -239,6 +240,13 @@ struct DivSong { // - 2: YM2423 // - 3: VRC7 // - 4: custom (TODO) + // - X1-010: + // - bit 0-3: clock rate + // - 0: 16MHz (Seta 1) + // - 1: 16.67MHz (Seta 2) + // - bit 4: stereo + // - 0: mono + // - 1: stereo unsigned int systemFlags[32]; // song information diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3c988ceb8..48de06c7f 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -135,6 +135,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_LYNX; case 0xa9: return DIV_SYSTEM_SEGAPCM_COMPAT; + case 0xb0: + return DIV_SYSTEM_X1_010; case 0xde: return DIV_SYSTEM_YM2610B_EXT; case 0xe0: @@ -258,6 +260,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0xa8; case DIV_SYSTEM_SEGAPCM_COMPAT: return 0xa9; + case DIV_SYSTEM_X1_010: + return 0xb0; case DIV_SYSTEM_YM2610B_EXT: return 0xde; case DIV_SYSTEM_QSOUND: @@ -335,7 +339,7 @@ int DivEngine::getChannelCount(DivSystem sys) { case DIV_SYSTEM_OPL3: return 18; case DIV_SYSTEM_MULTIPCM: - return 24; + return 28; case DIV_SYSTEM_PCSPKR: return 1; case DIV_SYSTEM_POKEY: @@ -351,6 +355,7 @@ int DivEngine::getChannelCount(DivSystem sys) { case DIV_SYSTEM_POKEMINI: return 1; case DIV_SYSTEM_SEGAPCM: + case DIV_SYSTEM_X1_010: return 16; case DIV_SYSTEM_VBOY: return 6; @@ -650,6 +655,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "Taito Arcade Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom QSound"; + case DIV_SYSTEM_X1_010: + return "Seta/Allumer X1-010"; } return "Unknown"; } @@ -775,6 +782,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "Yamaha YM2610B Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom DL-1425"; + case DIV_SYSTEM_X1_010: + return "Seta/Allumer X1-010"; } return "Unknown"; } @@ -857,7 +866,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) { sys!=DIV_SYSTEM_YM2151); } -const char* chanNames[38][24]={ +const char* chanNames[38][28]={ {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3) @@ -886,7 +895,7 @@ const char* chanNames[38][24]={ {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, // OPN {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, // PC-98 {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "FM 10", "FM 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, // OPL3 - {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24"}, // MultiPCM + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"}, // MultiPCM {"Square"}, // PC Speaker/Pokémon Mini {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"}, // Virtual Boy/SCC {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B @@ -898,7 +907,7 @@ const char* chanNames[38][24]={ {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3) }; -const char* chanShortNames[38][24]={ +const char* chanShortNames[38][28]={ {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759 {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3) @@ -927,7 +936,7 @@ const char* chanShortNames[38][24]={ {"F1", "F2", "F3", "S1", "S2", "S3"}, // OPN {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, // PC-98 {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"}, // OPL3 - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24"}, // MultiPCM + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"}, // MultiPCM {"SQ"}, // PC Speaker/Pokémon Mini {"CH1", "CH2", "CH3", "CH4", "CH5", "NO"}, // Virtual Boy/SCC {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B @@ -939,7 +948,7 @@ const char* chanShortNames[38][24]={ {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) }; -const int chanTypes[38][24]={ +const int chanTypes[39][28]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759 {0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3) @@ -968,7 +977,7 @@ const int chanTypes[38][24]={ {0, 0, 0, 1, 1, 1}, // OPN {0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4}, // PC-98 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound {1}, // PC Speaker/Pokémon Mini {3, 3, 3, 3, 3, 2}, // Virtual Boy/SCC {0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B @@ -978,9 +987,10 @@ const int chanTypes[38][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums {3, 3, 3, 3}, //Lynx {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) + {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, // X1-010 }; -const DivInstrumentType chanPrefType[44][24]={ +const DivInstrumentType chanPrefType[45][28]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759 {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3) @@ -1009,7 +1019,7 @@ const DivInstrumentType chanPrefType[44][24]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, // OPN {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // PC-98 {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, // OPL/OPL2/OPL3 - {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound {DIV_INS_STD}, // PC Speaker/Pokémon Mini {DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY}, // Virtual Boy {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B @@ -1025,6 +1035,7 @@ const DivInstrumentType chanPrefType[44][24]={ {DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z {DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3) + {DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010}, // X1-010 }; const char* DivEngine::getChannelName(int chan) { @@ -1129,6 +1140,7 @@ const char* DivEngine::getChannelName(int chan) { case DIV_SYSTEM_MULTIPCM: case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: + case DIV_SYSTEM_X1_010: return chanNames[28][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_PCSPKR: @@ -1268,6 +1280,7 @@ const char* DivEngine::getChannelShortName(int chan) { case DIV_SYSTEM_MULTIPCM: case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: + case DIV_SYSTEM_X1_010: return chanShortNames[28][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_PCSPKR: @@ -1438,6 +1451,9 @@ int DivEngine::getChannelType(int chan) { case DIV_SYSTEM_LYNX: return chanTypes[36][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_X1_010: + return chanTypes[38][dispatchChanOfChan[chan]]; + break; } return 1; } @@ -1588,6 +1604,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { case DIV_SYSTEM_LYNX: return chanPrefType[42][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_X1_010: + return chanPrefType[44][dispatchChanOfChan[chan]]; + break; } return DIV_INS_FM; } @@ -1616,6 +1635,7 @@ bool DivEngine::isVGMExportable(DivSystem which) { case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: + case DIV_SYSTEM_X1_010: return true; default: return false; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 97f427372..3c63f65c9 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -150,6 +150,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(3); } break; + case DIV_SYSTEM_X1_010: + for (int i=0; i<16; i++) { + w->writeC(0xc8); + w->writeS((isSecond?0x8000:0x0)+(i<<3)); + w->writeC(0); + } + break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: @@ -391,6 +398,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeS((isSecond?0x8000:0)|(write.addr&0xffff)); w->writeC(write.val); break; + case DIV_SYSTEM_X1_010: + w->writeC(0xc8); + w->writeS((isSecond?0x8000:0)|(write.addr&0x1fff)); + w->writeC(write.val); + break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: @@ -481,6 +493,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { int hasOPM=0; int hasSegaPCM=0; int segaPCMOffset=0xf8000d; + int hasX1010=0; int hasRFC=0; int hasOPN=0; int hasOPNA=0; @@ -552,6 +565,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { bool writePCESamples=false; bool writeADPCM=false; bool writeSegaPCM=false; + bool writeX1010=false; bool writeQSound=false; for (int i=0; ichipClock; + willExport[i]=true; + writeX1010=true; + } else if (!(hasX1010&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasX1010|=0x40000000; + howManyChips++; + } + break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: @@ -964,6 +990,16 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { w->write(qsoundMem,blockSize); } + if (writeX1010 && x1_010MemLen>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x91); + w->writeI(x1_010MemLen+8); + w->writeI(x1_010MemLen); + w->writeI(0); + w->write(x1_010Mem,x1_010MemLen); + } + // initialize streams int streamID=0; for (int i=0; iinPorta?colorOn:colorOff,">> InPorta"); break; } + case DIV_SYSTEM_X1_010: { + DivPlatformX1_010::Channel* ch=(DivPlatformX1_010::Channel*)data; + ImGui::Text("> X1-010"); + ImGui::Text("* freq: %.4x",ch->freq); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text("- note: %d",ch->note); + ImGui::Text("- wave: %d",ch->wave); + ImGui::Text("- sample: %d",ch->sample); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("- pan: %d",ch->pan); + ImGui::Text("* envelope:"); + ImGui::Text(" - shape: %d",ch->env.shape); + ImGui::Text(" - period: %.2x",ch->env.period); + ImGui::Text(" - slide: %.2x",ch->env.slide); + ImGui::Text(" - slidefrac: %.2x",ch->env.slidefrac); + ImGui::Text(" - autoEnvNum: %.2x",ch->autoEnvNum); + ImGui::Text(" - autoEnvDen: %.2x",ch->autoEnvDen); + ImGui::Text("- WaveBank: %d",ch->waveBank); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- outVol: %.2x",ch->outVol); + ImGui::Text("- Lvol: %.2x",ch->lvol); + ImGui::Text("- Rvol: %.2x",ch->rvol); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->envChanged?colorOn:colorOff,">> EnvChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + ImGui::TextColored(ch->furnacePCM?colorOn:colorOff,">> FurnacePCM"); + ImGui::TextColored(ch->pcm?colorOn:colorOff,">> PCM"); + 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"); + break; + } default: ImGui::Text("Unknown system! Help!"); break; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ed9900e15..cb0c6dc4a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1191,6 +1191,10 @@ void FurnaceGUI::drawInsList() { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); break; + case DIV_INS_X1_010: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + break; default: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d\n",i,ins->name,i); @@ -1347,7 +1351,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("notes:"); if (sample->loopStart>=0) { considerations=true; - ImGui::Text("- sample won't loop on Neo Geo ADPCM-A"); + ImGui::Text("- sample won't loop on Neo Geo ADPCM-A and X1-010"); if (sample->loopStart&1) { ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga"); } @@ -1363,10 +1367,18 @@ void FurnaceGUI::drawSampleEdit() { considerations=true; ImGui::Text("- sample length will be aligned and padded to 512 sample units on Neo Geo ADPCM."); } + if (sample->samples&4095) { + considerations=true; + ImGui::Text("- sample length will be aligned and padded to 4096 sample units on X1-010."); + } if (sample->samples>65535) { considerations=true; ImGui::Text("- maximum sample length on Sega PCM and QSound is 65536 samples"); } + if (sample->samples>131071) { + considerations=true; + ImGui::Text("- maximum sample length on X1-010 is 131072 samples"); + } if (sample->samples>2097151) { considerations=true; ImGui::Text("- maximum sample length on Neo Geo ADPCM is 2097152 samples"); @@ -2027,6 +2039,7 @@ void FurnaceGUI::drawStats() { String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024); String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); + String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024); ImGui::Text("ADPCM-A"); ImGui::SameLine(); ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str()); @@ -2036,6 +2049,9 @@ void FurnaceGUI::drawStats() { ImGui::Text("QSound"); ImGui::SameLine(); ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str()); + ImGui::Text("X1-010"); + ImGui::SameLine(); + ImGui::ProgressBar(((float)e->x1_010MemLen)/1048576.0f,ImVec2(-FLT_MIN,0),x1_010Usage.c_str()); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS; ImGui::End(); @@ -4635,6 +4651,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_AY8930); sysAddOption(DIV_SYSTEM_LYNX); sysAddOption(DIV_SYSTEM_QSOUND); + sysAddOption(DIV_SYSTEM_X1_010); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -4921,6 +4938,23 @@ bool FurnaceGUI::loop() { } rightClickable break; } + case DIV_SYSTEM_X1_010: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("16MHz (Seta 1)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~16))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("16.67MHz (Seta 2)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~16))|1,restart); + updateWindowTitle(); + } + bool x1_010Stereo=flags&16; + if (ImGui::Checkbox("Stereo",&x1_010Stereo)) { + e->setSysFlags(i,(flags&(~15))|(x1_010Stereo<<4),restart); + updateWindowTitle(); + } + break; + } case DIV_SYSTEM_GB: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: @@ -4974,6 +5008,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_AY8930); sysChangeOption(i,DIV_SYSTEM_LYNX); sysChangeOption(i,DIV_SYSTEM_QSOUND); + sysChangeOption(i,DIV_SYSTEM_X1_010); ImGui::EndMenu(); } } @@ -5557,6 +5592,7 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_INSTR_BEEPER,ImVec4(0.0f,1.0f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_SWAN,ImVec4(0.3f,0.5f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_MIKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_X1_010,ImVec4(0.3f,0.5f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f)); @@ -6286,6 +6322,12 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta/Allumer X1-010", { + DIV_SYSTEM_X1_010, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Game consoles"); @@ -6570,6 +6612,18 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 1", { + DIV_SYSTEM_X1_010, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 2", { + DIV_SYSTEM_X1_010, 64, 0, 1, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("DefleMask-compatible"); diff --git a/src/gui/gui.h b/src/gui/gui.h index 7d7f6b4a0..7f200e381 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -73,6 +73,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_BEEPER, GUI_COLOR_INSTR_SWAN, GUI_COLOR_INSTR_MIKEY, + GUI_COLOR_INSTR_X1_010, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_FM, GUI_COLOR_CHANNEL_PULSE, diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 4afa67a5c..f8306c200 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,7 +27,7 @@ #include #include "plot_nolerp.h" -const char* insTypes[24]={ +const char* insTypes[25]={ "Standard", "FM (4-operator)", "Game Boy", @@ -51,7 +51,8 @@ const char* insTypes[24]={ "POKEY", "PC Beeper", "WonderSwan", - "Atari Lynx" + "Atari Lynx", + "X1-010" }; const char* ssgEnvTypes[8]={ @@ -148,6 +149,10 @@ 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* oneBit[2]={ "on", NULL }; @@ -783,9 +788,9 @@ void FurnaceGUI::drawInsEdit() { } else { DivInstrument* ins=e->song.ins[curIns]; ImGui::InputText("Name",&ins->name); - if (ins->type<0 || ins->type>23) ins->type=DIV_INS_FM; + if (ins->type<0 || ins->type>24) ins->type=DIV_INS_FM; int insType=ins->type; - if (ImGui::Combo("Type",&insType,insTypes,24,24)) { + if (ImGui::Combo("Type",&insType,insTypes,25,25)) { ins->type=(DivInstrumentType)insType; } @@ -1380,11 +1385,18 @@ void FurnaceGUI::drawInsEdit() { int ex1Max=(ins->type==DIV_INS_AY8930)?8:0; int ex2Max=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?4:0; + bool ex2Bit=true; if (ins->type==DIV_INS_C64) { ex1Max=4; ex2Max=15; } + if (ins->type==DIV_INS_X1_010) { + dutyMax=0; + ex1Max=5; + ex2Max=63; + ex2Bit=false; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (settings.macroView==0) { // modern view @@ -1409,6 +1421,8 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Filter Mode",64,ins->std.ex1MacroOpen,true,filtModeBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else if (ins->type==DIV_INS_SAA1099) { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope",160,ins->std.ex1MacroOpen,true,saaEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + } else if (ins->type==DIV_INS_X1_010) { + NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope Mode",160,ins->std.ex1MacroOpen,true,x1_010EnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Duty",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } @@ -1417,13 +1431,13 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_C64) { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Resonance",64,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } else { - NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",64,ins->std.ex2MacroOpen,true,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); + NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",ex2Bit?64:160,ins->std.ex2MacroOpen,ex2Bit,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } } if (ins->type==DIV_INS_C64) { NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,2,"ex3","Special",32,ins->std.ex3MacroOpen,true,c64SpecialBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false); } - if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930) { + if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_X1_010) { NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,15,"ex3","AutoEnv Num",96,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,15,NULL,false); NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,15,"alg","AutoEnv Den",96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,15,NULL,false); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 3f0e532db..f34f67ac5 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -492,6 +492,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_BEEPER,"PC Beeper"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SWAN,"WonderSwan"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_MIKEY,"Lynx"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_X1_010,"X1-010"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); ImGui::TreePop(); } @@ -1159,6 +1160,7 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_INSTR_BEEPER); PUT_UI_COLOR(GUI_COLOR_INSTR_SWAN); PUT_UI_COLOR(GUI_COLOR_INSTR_MIKEY); + PUT_UI_COLOR(GUI_COLOR_INSTR_X1_010); PUT_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN); PUT_UI_COLOR(GUI_COLOR_CHANNEL_FM); PUT_UI_COLOR(GUI_COLOR_CHANNEL_PULSE); From d0c32a56be05446915c1c24a3e635c7ebd67ddde Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 03:06:01 +0900 Subject: [PATCH 009/105] Fix panning --- src/engine/platform/x1_010.cpp | 88 ++++++++++++++++------------------ src/engine/playback.cpp | 2 + 2 files changed, 42 insertions(+), 48 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 6d072d730..7119a67f2 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -306,7 +306,7 @@ void DivPlatformX1_010::updateEnvelope(int ch) { for (int i=0; i<128; i++) { if (wt->max<1 || wt->len<1) { envFill(ch,i); - } else if (stereo&&(chan[ch].env.flag.envHinv||chan[ch].env.flag.envSplit||chan[ch].env.flag.envVinv)) { // Stereo config + } 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 @@ -386,29 +386,27 @@ void DivPlatformX1_010::tick() { refreshControl(i); } } - if (stereo) { - 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) { - chan[i].envChanged=true; - } + 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) { + 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) { - 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) { + 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) { - 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) { + chan[i].envChanged=true; } - } + } } if (chan[i].std.hadEx2) { if (chan[i].env.shape!=chan[i].std.ex2) { @@ -437,12 +435,8 @@ void DivPlatformX1_010::tick() { } if (chan[i].envChanged) { if (!isMuted[i]) { - if (stereo) { - chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; - chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15; - } else { - chan[i].lvol=chan[i].rvol=chan[i].outVol; - } + chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; + chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15; } updateEnvelope(i); chan[i].envChanged=false; @@ -654,7 +648,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } break; case DIV_CMD_PANNING: { - if (stereo&&(chan[c.chan].pan!=c.value)) { + if (chan[c.chan].pan!=c.value) { chan[c.chan].pan=c.value; if (!isMuted[c.chan]) { chan[c.chan].envChanged=true; @@ -700,29 +694,27 @@ int DivPlatformX1_010::dispatch(DivCommand c) { refreshControl(c.chan); } } - if (stereo) { - 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) { - chan[c.chan].envChanged=true; - } + 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) { + 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) { - 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) { + 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) { - 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) { + chan[c.chan].envChanged=true; } - } + } break; } case DIV_CMD_X1_010_ENVELOPE_PERIOD: diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 2fba35336..9e4542211 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -260,6 +260,8 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe case 0x17: // PCM enable dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); break; + default: + return false; } break; default: From 666b061c8b0d3bc28be233c9ded6fb95181398c7 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 03:08:47 +0900 Subject: [PATCH 010/105] Fix year info --- papers/doc/7-systems/x1_010.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index 952e42316..83587a2b8 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -1,6 +1,6 @@ # Seta/Allumer X1-010 -One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-88 to early-2000s. +One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-80s to early-2000s. it has 2 output channel, but no known hardware using this feature for stereo sound. later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision. Allumer one is just rebadged Seta's thing for use in their arcade hardwares. From 789be838e34e2ce762f9f603f9a2ec058d08c0fe Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 03:43:44 +0900 Subject: [PATCH 011/105] Submodule update --- extern/cam900_vgsound_emu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/cam900_vgsound_emu b/extern/cam900_vgsound_emu index cf8816851..cf4ba11ad 160000 --- a/extern/cam900_vgsound_emu +++ b/extern/cam900_vgsound_emu @@ -1 +1 @@ -Subproject commit cf88168512c1b32bbea7b7dcc1411c4da429a723 +Subproject commit cf4ba11ad786f13c374afb77616d509cb4c751bf From 8da59211961b85dc0af8b677d5c778adc7902ecb Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 03:50:15 +0900 Subject: [PATCH 012/105] step 2 --- src/engine/platform/x1_010.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 7119a67f2..74bfdb13a 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -241,8 +241,8 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l for (size_t h=start; htick(); - signed short tempL=x1_010->output(0); - signed short tempR=x1_010->output(1); + signed int tempL=x1_010->output(0); + signed int tempR=x1_010->output(1); if (tempL<-32768) tempL=-32768; if (tempL>32767) tempL=32767; From 6c897722dbf439ed79acdc36812921317373df81 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 04:03:45 +0900 Subject: [PATCH 013/105] Compile fix Take 3 --- src/engine/playback.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 9e4542211..511e55e17 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -249,6 +249,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe } break; } + break; case DIV_SYSTEM_X1_010: switch (effect) { case 0x10: // select waveform From 458f8c5881bec3a6eca3a12ac352ed6989afa9b0 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 12:21:51 +0900 Subject: [PATCH 014/105] Fix instrument allocation --- src/engine/instrument.h | 4 +++- src/gui/insEdit.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index da7356f1e..abee4223a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -48,7 +48,9 @@ enum DivInstrumentType { DIV_INS_BEEPER=21, DIV_INS_SWAN=22, DIV_INS_MIKEY=23, - DIV_INS_X1_010=24, + DIV_INS_VERA=24, + DIV_INS_X1_010=25, + DIV_INS_MAX, }; // FM operator structure: diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 29085c561..35059804d 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -788,9 +788,9 @@ void FurnaceGUI::drawInsEdit() { } else { DivInstrument* ins=e->song.ins[curIns]; ImGui::InputText("Name",&ins->name); - if (ins->type<0 || ins->type>24) ins->type=DIV_INS_FM; + if (ins->type<0 || ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM; int insType=ins->type; - if (ImGui::Combo("Type",&insType,insTypes,25,25)) { + if (ImGui::Combo("Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) { ins->type=(DivInstrumentType)insType; } From 36647ac81d88d21a48d866ca087c6b96692c50a2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 13:03:39 +0900 Subject: [PATCH 015/105] Update submodule --- .gitmodules | 1 + extern/cam900_vgsound_emu | 2 +- src/engine/platform/x1_010.h | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 075769005..8c4c6b0dd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,6 +22,7 @@ [submodule "extern/cam900_vgsound_emu"] path = extern/cam900_vgsound_emu url = https://github.com/cam900/vgsound_emu + branch = main [submodule "extern/Nuked-OPL3"] path = extern/Nuked-OPL3 url = https://github.com/nukeykt/Nuked-OPL3.git diff --git a/extern/cam900_vgsound_emu b/extern/cam900_vgsound_emu index cf4ba11ad..3f8b5c5c6 160000 --- a/extern/cam900_vgsound_emu +++ b/extern/cam900_vgsound_emu @@ -1 +1 @@ -Subproject commit cf4ba11ad786f13c374afb77616d509cb4c751bf +Subproject commit 3f8b5c5c6b996588afec7e4ce7469abccf16ecbe diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 27bf4f4a6..fd00fd1c9 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -26,11 +26,11 @@ #include "../macroInt.h" #include "../../../extern/cam900_vgsound_emu/x1_010/x1_010.hpp" -class DivX1_010Interface: public x1_010_intf { +class DivX1_010Interface: public vgsound_emu_mem_intf { public: DivEngine* parent; int sampleBank; - virtual u8 read_rom(uint32_t address) override { + virtual u8 read_byte(u32 address) override { if (parent->x1_010Mem==NULL) return 0; return parent->x1_010Mem[address & 0xfffff]; } From 55934bc044f17b03cea4d97c036461b1174e32d5 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 13:09:25 +0900 Subject: [PATCH 016/105] Fix crash --- src/gui/insEdit.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 35059804d..a66a7f585 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,7 +27,7 @@ #include #include "plot_nolerp.h" -const char* insTypes[25]={ +const char* insTypes[DIV_INS_MAX]={ "Standard", "FM (4-operator)", "Game Boy", @@ -52,6 +52,7 @@ const char* insTypes[25]={ "PC Beeper", "WonderSwan", "Atari Lynx", + "VERA", "X1-010" }; From bc26fbaa3d1ac7d066f4fc7ab12a38fe300febef Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 13:34:13 +0900 Subject: [PATCH 017/105] Add cmdName for X1-010 commands --- src/engine/playback.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index acc4e7607..665409d67 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -117,6 +117,13 @@ const char* cmdName[DIV_CMD_MAX]={ "QSOUND_ECHO_DELAY", "QSOUND_ECHO_LEVEL", + "X1_010_ENVELOPE_SHAPE", + "X1_010_ENVELOPE_ENABLE", + "X1_010_ENVELOPE_MODE", + "X1_010_ENVELOPE_PERIOD", + "X1_010_ENVELOPE_SLIDE", + "X1_010_AUTO_ENVELOPE", + "ALWAYS_SET_VOLUME" }; From 252dc16492da387afc72c622c99a50660de6abf9 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 16:45:34 +0700 Subject: [PATCH 018/105] Add X16 to the New menu --- src/gui/gui.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f4fd48a28..79335dfdf 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6457,6 +6457,13 @@ FurnaceGUI::FurnaceGUI(): 0 } ));*/ + cat.systems.push_back(FurnaceGUISysDef( + "Commander X16", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_VERA, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Arcade systems"); From 7f3519b9708957b8e556b0d2314ac88b73e678fd Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 16:46:12 +0700 Subject: [PATCH 019/105] Implement VERA noise generation instead of rand() --- src/engine/platform/vera.cpp | 11 ++++++++--- src/engine/platform/vera.h | 5 ++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 47f4888d6..ce45341be 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -62,14 +62,17 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len int32_t lout=0; int32_t rout=0; // PSG + // TODO this is a currently speculated noise generation + // as the hardware and sources for it are not out in the public + // and the official emulator just uses rand() + noiseState=(noiseState<<1)|(((noiseState>>1)^(noiseState>>2)^(noiseState>>4)^(noiseState>>15))&1); + noiseOut=((noiseOut<<1)|(noiseState&1))&63; for (int i=0; i<16; i++) { unsigned freq=regPool[i*4+0] | (regPool[i*4+1] << 8); unsigned old_accum=chan[i].accum; unsigned new_accum=old_accum+freq; int val=0x20; - // TODO actually emulate the LFSR, it's currently unknown publicly - // and the official emulator just uses this: - if ((old_accum^new_accum)&0x10000) chan[i].noiseval=rand()&63; + if ((old_accum^new_accum)&0x10000) chan[i].noiseval=noiseOut; new_accum&=0x1ffff; chan[i].accum=new_accum; switch (regPool[i*4+3]>>6) { @@ -136,6 +139,8 @@ void DivPlatformVERA::reset() { rWriteHi(i,2,3); // default pan } chan[16].vol=15; + noiseState=1; + noiseOut=0; } int DivPlatformVERA::calcNoteFreq(int ch, int note) { diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index c6a208b90..64a8ee7c1 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -46,8 +46,10 @@ class DivPlatformVERA: public DivDispatch { }; Channel chan[17]; bool isMuted[17]; + unsigned noiseState, noiseOut; unsigned char regPool[66]; + int calcNoteFreq(int ch, int note); friend void putDispatchChan(void*,int,int); public: @@ -67,8 +69,5 @@ class DivPlatformVERA: public DivDispatch { const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); ~DivPlatformVERA(); - - private: - int calcNoteFreq(int ch, int note); }; #endif From b270513639dc06acef4b9d0de3c62871476cf8f2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 19:41:26 +0900 Subject: [PATCH 020/105] Frequency range limit --- papers/doc/7-systems/x1_010.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index 83587a2b8..7d5778713 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -15,7 +15,7 @@ In furnace, this chip is can be configurable for original arcade mono output or - `10xx`: change wave. - `11xx`: change envelope shape. (also wavetable) - `17xx`: toggle PCM mode. -- `20xx`: set PCM frequency.* +- `20xx`: set PCM frequency. (1 to FF)* - `22xx`: set envelope mode. - 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. From 65149a466f5ec69a9fe5a20b409c844ec956318a Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 8 Mar 2022 00:15:21 +0900 Subject: [PATCH 021/105] Fix accidently auto-generated spaces --- src/engine/engine.cpp | 4 +-- src/engine/fileOps.cpp | 8 +++--- src/engine/platform/x1_010.cpp | 44 ++++++++++++++++----------------- src/engine/platform/x1_010.h | 6 ++--- src/engine/platform/ym2610b.cpp | 2 +- src/engine/playback.cpp | 2 +- src/engine/sample.cpp | 2 +- src/engine/vgmOps.cpp | 2 +- src/gui/gui.cpp | 2 +- src/gui/insEdit.cpp | 12 ++++----- 10 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e484baf31..87594f339 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -541,7 +541,7 @@ void DivEngine::renderSamples() { for (int i=0; ilength8+4095)&(~0xfff); - // fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!) + // fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!) if (paddedLen>131072) { paddedLen=131072; } @@ -977,7 +977,7 @@ int DivEngine::getEffectiveSampleRate(int rate) { return 1789773/(1789773/rate); case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: return (31250*MIN(255,(rate*255/31250)))/255; - case DIV_SYSTEM_QSOUND: + case DIV_SYSTEM_QSOUND: return (24038*MIN(65535,(rate*4096/24038)))/4096; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: return 18518; // TODO: support ADPCM-B case diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index ec7cd1cb8..3a79cebed 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -149,8 +149,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { // Neo Geo detune if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT - || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT - || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { + || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT + || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { ds.tuning=443.23; } @@ -259,8 +259,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->type=DIV_INS_C64; } if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT - || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT - || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { + || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT + || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { if (!ins->mode) { ins->type=DIV_INS_AY; } diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 74bfdb13a..1705e8272 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -342,7 +342,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) { @@ -358,7 +358,7 @@ void DivPlatformX1_010::tick() { chan[i].freqChanged=true; } } - } + } if (chan[i].std.hadWave && !chan[i].pcm) { if (chan[i].wave!=chan[i].std.wave) { chan[i].wave=chan[i].std.wave; @@ -369,7 +369,7 @@ void DivPlatformX1_010::tick() { } } if (chan[i].std.hadEx1) { - bool nextEnable=(chan[i].std.ex1&1); + bool nextEnable=(chan[i].std.ex1&1); if (nextEnable!=(chan[i].env.flag.envEnable)) { chan[i].env.flag.envEnable=nextEnable; if (!chan[i].pcm) { @@ -379,28 +379,28 @@ void DivPlatformX1_010::tick() { refreshControl(i); } } - bool nextOneshot=(chan[i].std.ex1&2); + bool nextOneshot=(chan[i].std.ex1&2); if (nextOneshot!=(chan[i].env.flag.envOneshot)) { chan[i].env.flag.envOneshot=nextOneshot; if (!chan[i].pcm) { refreshControl(i); } } - bool nextSplit=(chan[i].std.ex1&4); + 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) { chan[i].envChanged=true; } } - bool nextHinv=(chan[i].std.ex1&8); + 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) { chan[i].envChanged=true; } } - bool nextVinv=(chan[i].std.ex1&16); + 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) { @@ -435,7 +435,7 @@ void DivPlatformX1_010::tick() { } if (chan[i].envChanged) { if (!isMuted[i]) { - chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; + chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15; } updateEnvelope(i); @@ -500,10 +500,10 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } if (skipRegisterWrites) break; if (chan[c.chan].furnacePCM) { - chan[c.chan].pcm=true; + 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) { + chan[c.chan].sample=ins->amiga.initSample; + 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 @@ -513,7 +513,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); chan[c.chan].freqChanged=true; } - } else { + } else { chan[c.chan].std.init(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { @@ -524,7 +524,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chWrite(c.chan,5,0); break; } - } + } } else { chan[c.chan].std.init(NULL); chan[c.chan].outVol=chan[c.chan].vol; @@ -552,7 +552,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chan[c.chan].envChanged=true; chan[c.chan].std.init(ins); - refreshControl(c.chan); + refreshControl(c.chan); break; } case DIV_CMD_NOTE_OFF: @@ -598,7 +598,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { updateWave(c.chan); chan[c.chan].keyOn=true; break; - case DIV_CMD_X1_010_ENVELOPE_SHAPE: + case DIV_CMD_X1_010_ENVELOPE_SHAPE: if (chan[c.chan].env.shape!=c.value) { chan[c.chan].env.shape=c.value; if (!chan[c.chan].pcm) { @@ -677,7 +677,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } break; case DIV_CMD_X1_010_ENVELOPE_MODE: { - bool nextEnable=c.value&1; + bool nextEnable=c.value&1; if (nextEnable!=(chan[c.chan].env.flag.envEnable)) { chan[c.chan].env.flag.envEnable=nextEnable; if (!chan[c.chan].pcm) { @@ -687,28 +687,28 @@ int DivPlatformX1_010::dispatch(DivCommand c) { refreshControl(c.chan); } } - bool nextOneshot=c.value&2; + bool nextOneshot=c.value&2; if (nextOneshot!=(chan[c.chan].env.flag.envOneshot)) { chan[c.chan].env.flag.envOneshot=nextOneshot; if (!chan[c.chan].pcm) { refreshControl(c.chan); } } - bool nextSplit=c.value&4; + 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) { chan[c.chan].envChanged=true; } } - bool nextHinv=c.value&8; + 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) { chan[c.chan].envChanged=true; } } - bool nextVinv=c.value&16; + 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) { @@ -716,7 +716,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } } break; - } + } case DIV_CMD_X1_010_ENVELOPE_PERIOD: chan[c.chan].env.period=c.value; if (!chan[c.chan].pcm) { @@ -816,7 +816,7 @@ void DivPlatformX1_010::setFlags(unsigned int flags) { case 1: // 16.67MHz (later hardwares) chipClock=50000000.0/3.0; break; - // Other clock is used? + // Other clock is used? } rate=chipClock/512; stereo=flags&16; diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index fd00fd1c9..8e03109ba 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -31,9 +31,9 @@ class DivX1_010Interface: public vgsound_emu_mem_intf { DivEngine* parent; int sampleBank; virtual u8 read_byte(u32 address) override { - if (parent->x1_010Mem==NULL) return 0; - return parent->x1_010Mem[address & 0xfffff]; - } + if (parent->x1_010Mem==NULL) return 0; + return parent->x1_010Mem[address & 0xfffff]; + } DivX1_010Interface(): parent(NULL), sampleBank(0) {} }; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index a6bcf9ac7..e7a6e45c2 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -752,7 +752,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].std.init(ins); if (!chan[c.chan].std.willVol) { chan[c.chan].outVol=chan[c.chan].vol; - immWrite(0x1b,chan[c.chan].outVol); + immWrite(0x1b,chan[c.chan].outVol); } DivSample* s=parent->getSample(ins->amiga.initSample); immWrite(0x12,(s->offB>>8)&0xff); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 665409d67..b1a820952 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -256,7 +256,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; - } + } break; case DIV_SYSTEM_X1_010: switch (effect) { diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index b802bf791..571e35118 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -115,7 +115,7 @@ bool DivSample::initInternal(unsigned char d, int count) { case 8: // 8-bit if (data8!=NULL) delete[] data8; length8=count; - // for padding X1-010 sample + // for padding X1-010 sample data8=new signed char[(count+4095)&(~0xfff)]; memset(data8,0,(count+4095)&(~0xfff)); break; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 3c63f65c9..74654f6ea 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -679,7 +679,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { } if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag hasOPNB|=0x80000000; - } + } break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 743f91083..50a2db78f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4959,7 +4959,7 @@ bool FurnaceGUI::loop() { updateWindowTitle(); } break; - } + } case DIV_SYSTEM_GB: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index a66a7f585..7ce11531e 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1392,12 +1392,12 @@ void FurnaceGUI::drawInsEdit() { ex1Max=4; ex2Max=15; } - if (ins->type==DIV_INS_X1_010) { - dutyMax=0; - ex1Max=5; - ex2Max=63; - ex2Bit=false; - } + if (ins->type==DIV_INS_X1_010) { + dutyMax=0; + ex1Max=5; + ex2Max=63; + ex2Bit=false; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (settings.macroView==0) { // modern view From 26470d594e27cfb8f67026b83a45d45871b8284c Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 8 Mar 2022 00:43:16 +0900 Subject: [PATCH 022/105] Actually PCM frequency limit --- src/engine/platform/x1_010.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 1705e8272..8fce448e8 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -669,7 +669,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { break; case DIV_CMD_SAMPLE_FREQ: if (chan[c.chan].pcm) { - chan[c.chan].freq=c.value&0xff; + chan[c.chan].freq=MAX(1,c.value&0xff); chWrite(c.chan,2,chan[c.chan].freq&0xff); if (chRead(c.chan,0)&1) { refreshControl(c.chan); From 3f4966096ab24348c84acfd6e903abcdd27fb1d4 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 8 Mar 2022 00:44:37 +0900 Subject: [PATCH 023/105] Fix info --- src/engine/platform/x1_010.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 8fce448e8..331b38420 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -216,7 +216,7 @@ const char* DivPlatformX1_010::getEffectName(unsigned char effect) { return "17xx: Toggle PCM mode"; break; case 0x20: - return "20xx: Set PCM frequency"; + 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)"; From a86a7f766be4325d73913d279951e66079859b28 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 8 Mar 2022 15:06:11 +0700 Subject: [PATCH 024/105] VERA doesn't have config flags --- src/gui/gui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f13ad55df..4a64bb3b5 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4928,6 +4928,7 @@ bool FurnaceGUI::loop() { break; } case DIV_SYSTEM_GB: + case DIV_SYSTEM_VERA: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: From e05052d9d7fbe0fdca88b3eedd7d2cf835473b27 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 8 Mar 2022 15:44:14 +0700 Subject: [PATCH 025/105] Properly case PCM channel --- src/engine/platform/vera.cpp | 39 +++++++++++++++--------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index ce45341be..4d0355731 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -161,21 +161,11 @@ int DivPlatformVERA::calcNoteFreq(int ch, int note) { } void DivPlatformVERA::tick() { - for (int i=0; i<17; i++) { + for (int i=0; i<16; i++) { chan[i].std.next(); if (chan[i].std.hadVol) { - if (i<16) { - chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); - rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63)); - } else { - // NB this is currently assuming Amiga instrument type with a 0-64 - // (inclusive) volume range. This envelope is then scaled and added to - // the channel volume. Is this a better way to handle this instead of - // making another identical Amiga instrument type but with a 0-15 - // volume range? - chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); - rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15)); - } + chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); + rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63)); } if (chan[i].std.hadArp) { if (!chan[i].inPorta) { @@ -192,28 +182,25 @@ void DivPlatformVERA::tick() { chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty && i<16) { + if (chan[i].std.hadDuty) { rWriteLo(i,3,chan[i].std.duty); } - if (chan[i].std.hadWave && i<16) { + if (chan[i].std.hadWave) { rWriteHi(i,3,chan[i].std.wave); } if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8); - if (i<16) { - if (chan[i].freq>65535) chan[i].freq=65535; - rWrite(i,0,chan[i].freq&0xff); - rWrite(i,1,(chan[i].freq>>8)&0xff); - } else { - if (chan[i].freq>128) chan[i].freq=128; - rWrite(16,1,chan[i].freq&0xff); - } + if (chan[i].freq>65535) chan[i].freq=65535; + rWrite(i,0,chan[i].freq&0xff); + rWrite(i,1,(chan[i].freq>>8)&0xff); chan[i].freqChanged=false; } } // PCM chan[16].std.next(); if (chan[16].std.hadVol) { + chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); + rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15)); } if (chan[16].std.hadArp) { if (!chan[16].inPorta) { @@ -230,6 +217,12 @@ void DivPlatformVERA::tick() { chan[16].freqChanged=true; } } + if (chan[16].freqChanged) { + chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8); + if (chan[16].freq>128) chan[16].freq=128; + rWrite(16,1,chan[16].freq&0xff); + chan[16].freqChanged=false; + } } int DivPlatformVERA::dispatch(DivCommand c) { From 8b1e557b5c9983f2ca1166c526781d9cf93059b6 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 8 Mar 2022 21:34:12 +0900 Subject: [PATCH 026/105] Sync with master --- src/engine/vgmOps.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index dc825f6a7..069d5a2ef 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -157,7 +157,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_X1_010: for (int i=0; i<16; i++) { w->writeC(0xc8); - w->writeS((isSecond?0x8000:0x0)+(i<<3)); + w->writeS(baseAddr2S+(i<<3)); w->writeC(0); } break; @@ -404,7 +404,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; case DIV_SYSTEM_X1_010: w->writeC(0xc8); - w->writeS((isSecond?0x8000:0)|(write.addr&0x1fff)); + w->writeS(baseAddr2S|(write.addr&0x1fff)); w->writeC(write.val); break; case DIV_SYSTEM_YM2610: From 6c432bc42e6ec1f2280c5931c1568b155fd2bd56 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 00:50:10 +0900 Subject: [PATCH 027/105] Allow Left waveform can be invertable, Improvement documents --- papers/doc/4-instrument/README.md | 2 +- papers/doc/5-wave/README.md | 2 +- papers/doc/7-systems/x1_010.md | 16 +++- src/engine/platform/x1_010.cpp | 120 +++++++++++++++++++----------- src/engine/platform/x1_010.h | 18 +++-- src/gui/debug.cpp | 6 +- src/gui/insEdit.cpp | 6 +- 7 files changed, 110 insertions(+), 60 deletions(-) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index 2b907634d..9a31cf65f 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 005739257..5e234bf65 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 7d5778713..e2e44f183 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 331b38420..e6b07d01b 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 8e03109ba..cb9ad7b42 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 fc2d51ea1..ddbfb5b57 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 0b7ce0ab2..260713e99 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; } From 66eb40e55e75b0f603cfcb240a381f3d25fc7279 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 01:00:09 +0900 Subject: [PATCH 028/105] Extract X1-010 core from submodule --- .gitmodules | 4 - CMakeLists.txt | 4 +- extern/cam900_vgsound_emu | 1 - src/engine/platform/sound/x1_010/x1_010.cpp | 224 ++++++++++++++++++++ src/engine/platform/sound/x1_010/x1_010.hpp | 127 +++++++++++ src/engine/platform/x1_010.h | 4 +- 6 files changed, 355 insertions(+), 9 deletions(-) delete mode 160000 extern/cam900_vgsound_emu create mode 100644 src/engine/platform/sound/x1_010/x1_010.cpp create mode 100644 src/engine/platform/sound/x1_010/x1_010.hpp diff --git a/.gitmodules b/.gitmodules index 8c4c6b0dd..d63fd70b5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,10 +19,6 @@ [submodule "extern/adpcm"] path = extern/adpcm url = https://github.com/superctr/adpcm -[submodule "extern/cam900_vgsound_emu"] - path = extern/cam900_vgsound_emu - url = https://github.com/cam900/vgsound_emu - branch = main [submodule "extern/Nuked-OPL3"] path = extern/Nuked-OPL3 url = https://github.com/nukeykt/Nuked-OPL3.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 23c8dffcf..364875867 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,8 +228,6 @@ extern/opm/opm.c extern/Nuked-OPLL/opll.c extern/Nuked-OPL3/opl3.c -extern/cam900_vgsound_emu/x1_010/x1_010.cpp - src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp @@ -268,6 +266,8 @@ src/engine/platform/sound/lynx/Mikey.cpp src/engine/platform/sound/qsound.c +src/engine/platform/sound/x1_010/x1_010.cpp + src/engine/platform/sound/swan.cpp src/engine/platform/ym2610Interface.cpp diff --git a/extern/cam900_vgsound_emu b/extern/cam900_vgsound_emu deleted file mode 160000 index 3f8b5c5c6..000000000 --- a/extern/cam900_vgsound_emu +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3f8b5c5c6b996588afec7e4ce7469abccf16ecbe diff --git a/src/engine/platform/sound/x1_010/x1_010.cpp b/src/engine/platform/sound/x1_010/x1_010.cpp new file mode 100644 index 000000000..ea1e52ace --- /dev/null +++ b/src/engine/platform/sound/x1_010/x1_010.cpp @@ -0,0 +1,224 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holders: cam900 + Seta/Allumer X1-010 Emulation core + + the chip has 16 voices, all voices can be switchable to Wavetable or PCM sample playback mode. + It has also 2 output channels, but no known hardware using this feature for stereo sound. + + Wavetable needs to paired with envelope, it's always enabled and similar as AY PSG's one + but its shape is stored at RAM. + + PCM volume is stored by each register. + + Both volume is 4bit per output. + + Everything except PCM sample is stored at paired 8 bit RAM. + + RAM layout (common case: Address bit 12 is swapped when RAM is shared with CPU) + + ----------------------------- + 0000...007f Voice Registers + + 0000...0007 Voice 0 Register + + Address Bits Description + 7654 3210 + 0 x--- ---- Frequency divider* + ---- -x-- Envelope one-shot mode + ---- --x- Sound format + ---- --0- PCM + ---- --1- Wavetable + ---- ---x Keyon/off + PCM case: + 1 xxxx xxxx Volume (Each nibble is for each output) + + 2 xxxx xxxx Frequency* + + 4 xxxx xxxx Start address / 4096 + + 5 xxxx xxxx 0x100 - (End address / 4096) + Wavetable case: + 1 ---x xxxx Wavetable data select + + 2 xxxx xxxx Frequency LSB* + 3 xxxx xxxx "" MSB + + 4 xxxx xxxx Envelope period (.10 fixed point, Low 8 bit) + + 5 ---x xxxx Envelope shape select (!= 0 : Reserved for Voice registers) + + 0008...000f Voice 1 Register + ... + 0078...007f Voice 15 Register + ----------------------------- + 0080...0fff Envelope shape data (Same as volume; Each nibble is for each output) + + 0080...00ff Envelope shape data 1 + 0100...017f Envelope shape data 2 + ... + 0f80...0fff Envelope shape data 31 + ----------------------------- + 1000...1fff Wavetable data + + 1000...107f Wavetable data 0 + 1080...10ff Wavetable data 1 + ... + 1f80...1fff Wavetable data 31 + ----------------------------- + + * Frequency is 4.4 fixed point for PCM, + 6.10 for Wavetable. + Frequency divider is higher precision or just right shift? + needs verification. +*/ + +#include "x1_010.hpp" + +void x1_010_core::tick() +{ + // reset output + m_out[0] = m_out[1] = 0; + for (int i = 0; i < 16; i++) + { + voice_t &v = m_voice[i]; + v.tick(); + m_out[0] += v.data * v.vol_out[0]; + m_out[1] += v.data * v.vol_out[1]; + } +} + +void x1_010_core::voice_t::tick() +{ + data = vol_out[0] = vol_out[1] = 0; + if (flag.keyon) + { + if (flag.wavetable) // Wavetable + { + // envelope, each nibble is for each output + u8 vol = m_host.m_envelope[(bitfield(end_envshape, 0, 5) << 7) | bitfield(env_acc, 10, 7)]; + vol_out[0] = bitfield(vol, 4, 4); + vol_out[1] = bitfield(vol, 0, 4); + env_acc += start_envfreq; + if (flag.env_oneshot && bitfield(env_acc, 17)) + flag.keyon = false; + else + env_acc = bitfield(env_acc, 0, 17); + // get wavetable data + data = m_host.m_wave[(bitfield(vol_wave, 0, 5) << 7) | bitfield(acc, 10, 7)]; + acc = bitfield(acc + (freq >> flag.div), 0, 17); + } + else // PCM sample + { + // volume register, each nibble is for each output + vol_out[0] = bitfield(vol_wave, 4, 4); + vol_out[1] = bitfield(vol_wave, 0, 4); + // get PCM sample + data = m_host.m_intf.read_byte(bitfield(acc, 4, 20)); + acc += bitfield(freq, 0, 8) >> flag.div; + if ((acc >> 16) > (0xff ^ end_envshape)) + flag.keyon = false; + } + } +} + +u8 x1_010_core::ram_r(u16 offset) +{ + if (offset & 0x1000) // wavetable data + return m_wave[offset & 0xfff]; + else if (offset & 0xf80) // envelope shape data + return m_envelope[offset & 0xfff]; + else // channel register + return m_voice[bitfield(offset, 3, 4)].reg_r(offset & 0x7); +} + +void x1_010_core::ram_w(u16 offset, u8 data) +{ + if (offset & 0x1000) // wavetable data + m_wave[offset & 0xfff] = data; + else if (offset & 0xf80) // envelope shape data + m_envelope[offset & 0xfff] = data; + else // channel register + m_voice[bitfield(offset, 3, 4)].reg_w(offset & 0x7, data); +} + +u8 x1_010_core::voice_t::reg_r(u8 offset) +{ + switch (offset & 0x7) + { + case 0x00: return (flag.div << 7) + | (flag.env_oneshot << 2) + | (flag.wavetable << 1) + | (flag.keyon << 0); + case 0x01: return vol_wave; + case 0x02: return bitfield(freq, 0, 8); + case 0x03: return bitfield(freq, 8, 8); + case 0x04: return start_envfreq; + case 0x05: return end_envshape; + default: break; + } + return 0; +} + +void x1_010_core::voice_t::reg_w(u8 offset, u8 data) +{ + switch (offset & 0x7) + { + case 0x00: + { + const bool prev_keyon = flag.keyon; + flag.div = bitfield(data, 7); + flag.env_oneshot = bitfield(data, 2); + flag.wavetable = bitfield(data, 1); + flag.keyon = bitfield(data, 0); + if (!prev_keyon && flag.keyon) // Key on + { + acc = flag.wavetable ? 0 : (u32(start_envfreq) << 16); + env_acc = 0; + } + break; + } + case 0x01: + vol_wave = data; + break; + case 0x02: + freq = (freq & 0xff00) | data; + break; + case 0x03: + freq = (freq & 0x00ff) | (u16(data) << 8); + break; + case 0x04: + start_envfreq = data; + break; + case 0x05: + end_envshape = data; + break; + default: + break; + } +} + +void x1_010_core::voice_t::reset() +{ + flag.reset(); + vol_wave = 0; + freq = 0; + start_envfreq = 0; + end_envshape = 0; + acc = 0; + env_acc = 0; + data = 0; + vol_out[0] = vol_out[1] = 0; +} + +void x1_010_core::reset() +{ + for (int i = 0; i < 16; i++) + m_voice[i].reset(); + + std::fill_n(&m_envelope[0], 0x1000, 0); + std::fill_n(&m_wave[0], 0x1000, 0); + m_out[0] = m_out[1] = 0; +} diff --git a/src/engine/platform/sound/x1_010/x1_010.hpp b/src/engine/platform/sound/x1_010/x1_010.hpp new file mode 100644 index 000000000..d5c429fda --- /dev/null +++ b/src/engine/platform/sound/x1_010/x1_010.hpp @@ -0,0 +1,127 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holders: cam900 + Seta/Allumer X1-010 Emulation core + + See x1_010.cpp for more info. +*/ + +#include +#include + +#ifndef _VGSOUND_EMU_X1_010_HPP +#define _VGSOUND_EMU_X1_010_HPP + +#pragma once + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef signed char s8; +typedef signed int s32; + +template T bitfield(T in, u8 pos, u8 len = 1) +{ + return (in >> pos) & (len ? (T(1 << len) - 1) : 1); +} + +class x1_010_mem_intf +{ +public: + virtual u8 read_byte(u32 address) { return 0; } +}; + +class x1_010_core +{ + friend class x1_010_mem_intf; +public: + // constructor + x1_010_core(x1_010_mem_intf &intf) + : m_voice{*this,*this,*this,*this, + *this,*this,*this,*this, + *this,*this,*this,*this, + *this,*this,*this,*this} + , m_intf(intf) + { + m_envelope = std::make_unique(0x1000); + m_wave = std::make_unique(0x1000); + + std::fill_n(&m_envelope[0], 0x1000, 0); + std::fill_n(&m_wave[0], 0x1000, 0); + } + + // register accessor + u8 ram_r(u16 offset); + void ram_w(u16 offset, u8 data); + + // getters + s32 output(u8 channel) { return m_out[channel & 1]; } + + // internal state + void reset(); + void tick(); + +private: + // 16 voices in chip + struct voice_t + { + // constructor + voice_t(x1_010_core &host) : m_host(host) {} + + // internal state + void reset(); + void tick(); + + // register accessor + u8 reg_r(u8 offset); + void reg_w(u8 offset, u8 data); + + // registers + x1_010_core &m_host; + struct flag_t + { + u8 div : 1; + u8 env_oneshot : 1; + u8 wavetable : 1; + u8 keyon : 1; + void reset() + { + div = 0; + env_oneshot = 0; + wavetable = 0; + keyon = 0; + } + flag_t() + : div(0) + , env_oneshot(0) + , wavetable(0) + , keyon(0) + { } + }; + flag_t flag; + u8 vol_wave = 0; + u16 freq = 0; + u8 start_envfreq = 0; + u8 end_envshape = 0; + + // internal registers + u32 acc = 0; + u32 env_acc = 0; + s8 data = 0; + u8 vol_out[2] = {0}; + }; + voice_t m_voice[16]; + + // RAM + std::unique_ptr m_envelope = nullptr; + std::unique_ptr m_wave = nullptr; + + // output data + s32 m_out[2] = {0}; + + x1_010_mem_intf &m_intf; +}; + +#endif diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index cb9ad7b42..73cbaf6b1 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -24,9 +24,9 @@ #include "../dispatch.h" #include "../engine.h" #include "../macroInt.h" -#include "../../../extern/cam900_vgsound_emu/x1_010/x1_010.hpp" +#include "sound/x1_010/x1_010.hpp" -class DivX1_010Interface: public vgsound_emu_mem_intf { +class DivX1_010Interface: public x1_010_mem_intf { public: DivEngine* parent; int sampleBank; From 75b635229ce50c08edd8dcede663acc2919c208c Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 01:01:40 +0900 Subject: [PATCH 029/105] Unnecessary changes --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 364875867..eafb87623 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,7 +227,6 @@ extern/Nuked-OPN2/ym3438.c extern/opm/opm.c extern/Nuked-OPLL/opll.c extern/Nuked-OPL3/opl3.c - src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp From ba68ad6ed52e5b79e2b226665f890f15a1a2f242 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 01:06:47 +0900 Subject: [PATCH 030/105] More info in waveform size --- papers/doc/7-systems/x1_010.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index e2e44f183..76517b642 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -21,6 +21,7 @@ Another one ("Envelope") is 4 bit stereo waveform, its multiplied with above and 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. +in furnace, You can set envelope shape split mode. when it sets, its waveform will be splitted to left half and right half for each outputs. each max sizes are 128 bytes, total 256 bytes. # effects From a32781bb1a1d02d4bf45775247f78d58f5d1d327 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 01:17:16 +0900 Subject: [PATCH 031/105] grammar --- papers/doc/7-systems/x1_010.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index 76517b642..759d519c9 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -1,27 +1,27 @@ # Seta/Allumer X1-010 One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-80s to early-2000s. -it has 2 output channel, but no known hardware using this feature for stereo sound. -later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision. +It has 2 output channels, but no known hardware using this feature for stereo sound. +Later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision. Allumer one is just rebadged Seta's thing for use in their arcade hardwares. It has 16 channels, and all channels can be switchable to PCM sample or wavetable playback mode. -Wavetable needs to paired with envelope, this feature is similar as AY PSG but it's shape are stored at RAM: it means it is user-definable. +Wavetable needs to paired with envelope, this feature is similar as AY PSG, but its shape are stored at RAM: it means it is user-definable. -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. +In furnace, this chip is can be configurable for original arcade mono output or stereo output - it 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: +This chip supports 2 type waveforms, needs to paired external 8 KB RAM for use these features: -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. +One is signed 8 bit mono waveform, it's operated like other wavetable based sound systems. +These are stored at the 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. +Another one ("Envelope") is 4 bit stereo waveform, it's multiplied with above and calculates final output, Each nibble is used for each output channels. +These are stored at the 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. -in furnace, You can set envelope shape split mode. when it sets, its waveform will be splitted to left half and right half for each outputs. each max sizes are 128 bytes, total 256 bytes. +Both waveforms are 128 byte fixed size, it's freely allocated at each half of RAM except channel register area: each half can be stored total 32/31 waveforms at once. +In furnace, You can set envelope shape split mode. When it sets, its waveform will be split to left half and right half for each outputs. each max size are 128 bytes, total 256 bytes. # effects From eb3a73c38b552d010c9e621341159a1beb42310a Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Wed, 9 Mar 2022 00:58:21 +0700 Subject: [PATCH 032/105] Mute on pan registers instead --- src/engine/platform/vera.cpp | 57 +++++++++++++++++++++--------------- src/engine/platform/vera.h | 8 ++--- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 4d0355731..49255be80 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -93,19 +93,27 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len if (chan[16].accum>=128) { DivSample* s=parent->getSample(chan[16].pcm.sample); if (s->samples>0) { - // TODO stereo samples once DivSample has a support for it - switch (s->depth) { - case 8: - chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data8[chan[16].pcm.pos]*256):0; - chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data8[chan[16].pcm.pos]*256):0; - regPool[64]|=0x20; // for register viewer purposes - break; - case 16: - chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data16[chan[16].pcm.pos]):0; - chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data16[chan[16].pcm.pos]):0; - regPool[64]&=~0x20; - break; + int tmp_l=0; + int tmp_r=0; + if (!isMuted[16]) { + // TODO stereo samples once DivSample has a support for it + switch (s->depth) { + case 8: + tmp_l=s->data8[chan[16].pcm.pos]*256; + tmp_r=tmp_l; + regPool[64]|=0x20; // for register viewer purposes + break; + case 16: + tmp_l=s->data16[chan[16].pcm.pos]; + tmp_r=tmp_l; + regPool[64]&=~0x20; + break; + } + if (!(chan[16].pan&1)) tmp_l=0; + if (!(chan[16].pan&2)) tmp_r=0; } + chan[16].pcm.out_l=tmp_l; + chan[16].pcm.out_r=tmp_r; } else { chan[16].pcm.sample=-1; } @@ -136,9 +144,11 @@ void DivPlatformVERA::reset() { memset(regPool,0,66); for (int i=0; i<16; i++) { chan[i].vol=63; - rWriteHi(i,2,3); // default pan + chan[i].pan=3; + rWriteHi(i,2,isMuted[i]?0:3); } chan[16].vol=15; + chan[16].pan=3; noiseState=1; noiseOut=0; } @@ -165,7 +175,7 @@ void DivPlatformVERA::tick() { chan[i].std.next(); if (chan[i].std.hadVol) { chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); - rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63)); + rWriteLo(i,2,chan[i].outVol); } if (chan[i].std.hadArp) { if (!chan[i].inPorta) { @@ -200,7 +210,7 @@ void DivPlatformVERA::tick() { chan[16].std.next(); if (chan[16].std.hadVol) { chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); - rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15)); + rWriteFIFOVol(chan[16].outVol&15); } if (chan[16].std.hadArp) { if (!chan[16].inPorta) { @@ -229,16 +239,15 @@ int DivPlatformVERA::dispatch(DivCommand c) { int tmp; switch (c.cmd) { case DIV_CMD_NOTE_ON: - tmp = isMuted[c.chan]?0:chan[c.chan].vol; if(c.chan<16) { - rWriteLo(c.chan,2,tmp) + rWriteLo(c.chan,2,chan[c.chan].vol) } else { chan[c.chan].pcm.sample=parent->getIns(chan[16].ins)->amiga.initSample; if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { chan[c.chan].pcm.sample=-1; } chan[16].pcm.pos=0; - rWriteFIFOVol(tmp); + rWriteFIFOVol(chan[c.chan].vol); } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value); @@ -270,11 +279,11 @@ int DivPlatformVERA::dispatch(DivCommand c) { if (c.chan<16) { tmp=c.value&0x3f; chan[c.chan].vol=tmp; - rWriteLo(c.chan,2,(isMuted[c.chan]?0:tmp)); + rWriteLo(c.chan,2,tmp); } else { tmp=c.value&0x0f; chan[c.chan].vol=tmp; - rWriteFIFOVol(isMuted[c.chan]?0:tmp); + rWriteFIFOVol(tmp); } break; case DIV_CMD_GET_VOLUME: @@ -328,10 +337,9 @@ int DivPlatformVERA::dispatch(DivCommand c) { tmp=0; tmp|=(c.value&0x10)?1:0; tmp|=(c.value&0x01)?2:0; + chan[c.chan].pan=tmp&3; if (c.chan<16) { - rWriteHi(c.chan,2,tmp); - } else { - chan[c.chan].pcm.pan = tmp&3; + rWriteHi(c.chan,2,isMuted[c.chan]?0:chan[c.chan].pan); } break; } @@ -365,6 +373,9 @@ int DivPlatformVERA::getRegisterPoolSize() { void DivPlatformVERA::muteChannel(int ch, bool mute) { isMuted[ch]=mute; + if (ch<16) { + rWriteHi(ch,2,mute?0:chan[ch].pan); + } } bool DivPlatformVERA::isStereo() { diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index 64a8ee7c1..e5cb2ad9e 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -27,7 +27,7 @@ class DivPlatformVERA: public DivDispatch { protected: struct Channel { int freq, baseFreq, pitch, note; - unsigned char ins; + unsigned char ins, pan; bool active, freqChanged, inPorta; int vol, outVol; unsigned accum; @@ -39,10 +39,10 @@ class DivPlatformVERA: public DivDispatch { int out_l, out_r; unsigned pos; unsigned len; - unsigned char freq, pan; - PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0), pan(3) {} + unsigned char freq; + PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0) {} } pcm; - Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {} + Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), pan(0), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {} }; Channel chan[17]; bool isMuted[17]; From 0826f22d8ba839c6ca625241ae71227570dd6c05 Mon Sep 17 00:00:00 2001 From: nicco1690 <78063037+nicco1690@users.noreply.github.com> Date: Tue, 8 Mar 2022 22:42:43 -0500 Subject: [PATCH 033/105] Update docs to add more info Thanks to @freq-mod for providing the effect commands section and some more general information which has been copied into this version. --- papers/doc/7-systems/lynx.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/papers/doc/7-systems/lynx.md b/papers/doc/7-systems/lynx.md index c9c9bc26d..2e2f3e3c5 100644 --- a/papers/doc/7-systems/lynx.md +++ b/papers/doc/7-systems/lynx.md @@ -2,11 +2,19 @@ The Atari Lynx is a 16 bit handheld console developed by (obviously) Atari Corporation, and initially released in September of 1989, with the worldwide release being in 1990. +The Lynx, while being an incredible handheld for the time (and a lot more powerful than a Game Boy), unfortunately meant nothing in the end due to the Lynx being a market failiure, and ending up as one of the things that contributed to the downfall of Atari. + +Although the Lynx is still getting (rather impressive) homebrew developed for it, it does not mean that the Lynx is a popular system at all. + The Atari Lynx's custom sound chip and CPU (MIKEY) is a 6502-based 8 bit CPU running at 16MHz, however this information is generally not useful in the context of Furnace. ## Sound capabilities - The MIKEY has 4 channels of square wave-based sound, which can be modulated with different frequencies (×0, ×1, ×2, ×3, ×4, ×5, ×7, ×10, and ×11) to create wavetable-like results. + - Likewise, when a lot of the modulators are activated, this can provide a "pseudo-white noise"-like effect, whoch can be useful for drums and sound effects. - The MIKEY also has hard stereo panning capabilities via the `08xx` effect command. - The MIKEY has four 8-bit DACs (Digital to Analog Converter) — one for each voice — that essentially mean you can play samples on the MIKEY (at the cost of CPU time and memory). - The MIKEY also has a variety of pitches to choose from, and they go from 32Hz to "above the range of human hearing", according to Atari. + +## Effect commands + - `3xxx`: Load LFSR (0 to FFF). For it to work, duty macro in instrument editor must be set to some value, without it LFSR will not be fed with any bits. From f372088aad049c4268a69d4b8dab7394bafbf6c0 Mon Sep 17 00:00:00 2001 From: nicco1690 <78063037+nicco1690@users.noreply.github.com> Date: Tue, 8 Mar 2022 23:15:52 -0500 Subject: [PATCH 034/105] Create OPL3 docs (read Effect Commands section) --- papers/doc/7-systems/opl3.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 papers/doc/7-systems/opl3.md diff --git a/papers/doc/7-systems/opl3.md b/papers/doc/7-systems/opl3.md new file mode 100644 index 000000000..79fa9df05 --- /dev/null +++ b/papers/doc/7-systems/opl3.md @@ -0,0 +1,22 @@ +# Yamaha OPL3/YMF262 + +The Yamaha OPL3/YMF262 was an FM sound chip developed by Yamaha (obviously) and released in 1994. + +The OPL3 saw most of its use in PC sound cards (such as the SoundBlaster 16 and the Pro AudioSpectrum). + +Unfortunately, developers who wanted to port their OPL/OPL2 music to the OPL3 were very lazy in doing so, so most of them ended up disreguarding the additions to the OPL3 entirely, and would use entirely the same MIDI driver and patches. + +## Sound capabilities + + - 18 channels 2-op of FM synthesis + - 8 unique waveforms which can be used on the carrier or the modulator + - A "split operators" mode that makes it so that the first and second operators are their own "voices" (each take the base pitch of the note to play and add the frequency multiplier's pitch to it) + - Hard panning (left, center, and right only) + - A rhythm mode where the last 3 voices are replaced with 5 drum channels + - A 4-op mode where 12 FM channels are combined to make 6 4-op FM channels + - A "tremolo" mode where AM (amplitude modulation) is applied, but unfortunately without any strength or speed controls. + - A built in vibrato mode which enables vibrato without a music driver doing it for it + - ADSR support on the carrier and/or modulator + +## Effect commands + - PLEASE DO NOT MERGE UNTIL I HAVE COMMITED THE EFFECT COMMANDS TO THIS PULL REQUEST. From 6bb9843fb96b446f403e75564c85f13e91379f91 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Wed, 9 Mar 2022 18:06:07 +0700 Subject: [PATCH 035/105] Fix wrong noise sampling operation This really shouldn't have an effect on anything though... --- src/engine/platform/vera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 49255be80..3376b0a39 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -65,8 +65,8 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len // TODO this is a currently speculated noise generation // as the hardware and sources for it are not out in the public // and the official emulator just uses rand() - noiseState=(noiseState<<1)|(((noiseState>>1)^(noiseState>>2)^(noiseState>>4)^(noiseState>>15))&1); noiseOut=((noiseOut<<1)|(noiseState&1))&63; + noiseState=(noiseState<<1)|(((noiseState>>1)^(noiseState>>2)^(noiseState>>4)^(noiseState>>15))&1); for (int i=0; i<16; i++) { unsigned freq=regPool[i*4+0] | (regPool[i*4+1] << 8); unsigned old_accum=chan[i].accum; From 2fb6ea021b7cca10018c878be672106ccde72385 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 9 Mar 2022 15:43:30 -0500 Subject: [PATCH 036/105] GUI: this menu is packed with features that DON'T WORK YET --- src/gui/gui.cpp | 5 +++++ src/gui/gui.h | 2 ++ src/gui/pattern.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 25d662d05..7317385bc 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6135,6 +6135,11 @@ FurnaceGUI::FurnaceGUI(): bindSetPending(false), nextScroll(-1.0f), nextAddScroll(0.0f), + transposeAmount(0), + randomizeMin(0), + randomizeMax(255), + scaleMin(0.0f), + scaleMax(100.0f), oldOrdersLen(0) { // octave 1 diff --git a/src/gui/gui.h b/src/gui/gui.h index 7d7f6b4a0..e64fe4a04 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -650,6 +650,8 @@ class FurnaceGUI { ImVec2 threeChars, twoChars; SelectionPoint sel1, sel2; int dummyRows, demandX; + int transposeAmount, randomizeMin, randomizeMax; + float scaleMin, scaleMax; int oldOrdersLen; DivOrders oldOrders; diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 9bf6eee59..2b3f2d6a4 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -817,6 +817,24 @@ void FurnaceGUI::drawPattern() { ImGui::Selectable("cut"); ImGui::Selectable("copy"); ImGui::Selectable("paste"); + if (ImGui::BeginMenu("paste special...")) { + ImGui::Selectable("paste mix"); + ImGui::Selectable("paste mix (background)"); + ImGui::Selectable("paste flood"); + ImGui::Selectable("paste overflow"); + ImGui::EndMenu(); + } + ImGui::Selectable("delete"); + ImGui::Separator(); + + ImGui::InputInt("##TransposeAmount",&transposeAmount,1,1); + ImGui::SameLine(); + ImGui::Button("Transpose"); + + ImGui::Separator(); + ImGui::Selectable("interpolate"); + ImGui::Selectable("fade in"); + ImGui::Selectable("fade out"); if (ImGui::BeginMenu("change instrument...")) { if (e->song.ins.empty()) { ImGui::Text("no instruments available"); @@ -828,6 +846,26 @@ void FurnaceGUI::drawPattern() { } ImGui::EndMenu(); } + if (ImGui::BeginMenu("scale...")) { + ImGui::InputFloat("Bottom",&scaleMin,1,1); + ImGui::InputFloat("Top",&scaleMax,1,1); + ImGui::Button("Scale"); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("randomize...")) { + ImGui::InputInt("Minimum",&randomizeMin,1,1); + ImGui::InputInt("Maximum",&randomizeMax,1,1); + ImGui::Button("Randomize"); + ImGui::EndMenu(); + } + ImGui::Selectable("invert values"); + + ImGui::SameLine(); + + ImGui::Selectable("flip selection"); + ImGui::Selectable("collapse"); + ImGui::Selectable("expand"); + ImGui::EndPopup(); } } From b80b33ac8e46a4b345f047e262ff9a8fd85ff574 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 9 Mar 2022 16:42:15 -0500 Subject: [PATCH 037/105] GUI: demand scroll X in more situations --- src/gui/gui.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 7317385bc..a2cd182ce 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2479,6 +2479,7 @@ void FurnaceGUI::moveCursorPrevChannel(bool overflow) { selStart=cursor; selEnd=cursor; + demandScrollX=true; } void FurnaceGUI::moveCursorNextChannel(bool overflow) { @@ -2501,6 +2502,7 @@ void FurnaceGUI::moveCursorNextChannel(bool overflow) { selStart=cursor; selEnd=cursor; + demandScrollX=true; } void FurnaceGUI::moveCursorTop(bool select) { @@ -2510,6 +2512,7 @@ void FurnaceGUI::moveCursorTop(bool select) { DETERMINE_FIRST; cursor.xCoarse=firstChannel; cursor.xFine=0; + demandScrollX=true; } else { cursor.y=0; } @@ -2527,6 +2530,7 @@ void FurnaceGUI::moveCursorBottom(bool select) { DETERMINE_LAST; cursor.xCoarse=lastChannel-1; cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; + demandScrollX=true; } else { cursor.y=e->song.patLen-1; } From 93b3e1621396c4238c09d72cdaaf70fcb0ff5c2d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 9 Mar 2022 18:03:15 -0500 Subject: [PATCH 038/105] i need to go TODO: - EDIT LATCH - EDIT MASK --- src/gui/gui.cpp | 106 +++++++++++++++++++++++++++++++++++++++----- src/gui/gui.h | 27 ++++++++++- src/gui/pattern.cpp | 54 +--------------------- 3 files changed, 122 insertions(+), 65 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index a2cd182ce..a1fad1539 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2861,7 +2861,7 @@ void FurnaceGUI::doCopy(bool cut) { } } -void FurnaceGUI::doPaste() { +void FurnaceGUI::doPaste(PasteMode mode) { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_PASTE); char* clipText=SDL_GetClipboardText(); @@ -4394,6 +4394,99 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { #define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str() +void FurnaceGUI::editOptions(bool topMenu) { + char id[4096]; + if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true); + if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_PAT_COPY))) doCopy(false); + if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_PAT_PASTE))) doPaste(); + if (ImGui::BeginMenu("paste special...")) { + ImGui::MenuItem("paste mix",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX)); + ImGui::MenuItem("paste mix (background)",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX_BG)); + ImGui::MenuItem("paste flood",BIND_FOR(GUI_ACTION_PAT_PASTE_FLOOD)); + ImGui::MenuItem("paste overflow",BIND_FOR(GUI_ACTION_PAT_PASTE_OVERFLOW)); + ImGui::EndMenu(); + } + if (ImGui::MenuItem("delete",BIND_FOR(GUI_ACTION_PAT_DELETE))) doDelete(); + if (topMenu) { + if (ImGui::MenuItem("select all",BIND_FOR(GUI_ACTION_PAT_SELECT_ALL))) doSelectAll(); + } + ImGui::Separator(); + + if (ImGui::MenuItem("set latch",BIND_FOR(GUI_ACTION_PAT_LATCH))) { + // TODO + } + ImGui::Separator(); + + if (ImGui::MenuItem("note up",BIND_FOR(GUI_ACTION_PAT_NOTE_UP))) doTranspose(1); + if (ImGui::MenuItem("note down",BIND_FOR(GUI_ACTION_PAT_NOTE_DOWN))) doTranspose(-1); + if (ImGui::MenuItem("octave up",BIND_FOR(GUI_ACTION_PAT_OCTAVE_UP))) doTranspose(12); + if (ImGui::MenuItem("octave down",BIND_FOR(GUI_ACTION_PAT_OCTAVE_DOWN))) doTranspose(-12); + if (ImGui::InputInt("##TransposeAmount",&transposeAmount,1,1)) { + if (transposeAmount<-96) transposeAmount=-96; + if (transposeAmount>96) transposeAmount=96; + } + ImGui::SameLine(); + if (ImGui::Button("Transpose")) { + doTranspose(transposeAmount); + ImGui::CloseCurrentPopup(); + } + + ImGui::Separator(); + ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE)); + ImGui::MenuItem("fade in",BIND_FOR(GUI_ACTION_PAT_FADE_IN)); + ImGui::MenuItem("fade out",BIND_FOR(GUI_ACTION_PAT_FADE_OUT)); + if (ImGui::BeginMenu("change instrument...")) { + if (e->song.ins.empty()) { + ImGui::Text("no instruments available"); + } + for (size_t i=0; isong.ins.size(); i++) { + snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); + if (ImGui::MenuItem(id)) { // TODO + } + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("scale...")) { + if (ImGui::InputFloat("Bottom",&scaleMin,1,1,"%.1f%%")) { + if (scaleMin<0.0f) scaleMin=0.0f; + if (scaleMin>100.0f) scaleMin=100.0f; + } + if (ImGui::InputFloat("Top",&scaleMax,1,1,"%.1f%%")) { + if (scaleMax<0.0f) scaleMax=0.0f; + if (scaleMax>100.0f) scaleMax=100.0f; + } + if (ImGui::Button("Scale")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("randomize...")) { + ImGui::InputInt("Minimum",&randomizeMin,1,1); + ImGui::InputInt("Maximum",&randomizeMax,1,1); + if (ImGui::Button("Randomize")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } + ImGui::MenuItem("invert values",BIND_FOR(GUI_ACTION_PAT_INVERT_VALUES)); + + ImGui::Separator(); + + ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION)); + ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS)); + ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS)); + + if (topMenu) { + ImGui::Separator(); + ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT)); + ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT)); + + ImGui::Separator(); + ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG)); + ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG)); + } +} + bool FurnaceGUI::loop() { while (!quit) { SDL_Event ev; @@ -5028,16 +5121,7 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("undo",BIND_FOR(GUI_ACTION_UNDO))) doUndo(); if (ImGui::MenuItem("redo",BIND_FOR(GUI_ACTION_REDO))) doRedo(); ImGui::Separator(); - if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true); - if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_PAT_COPY))) doCopy(false); - if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_PAT_PASTE))) doPaste(); - if (ImGui::MenuItem("delete",BIND_FOR(GUI_ACTION_PAT_DELETE))) doDelete(); - if (ImGui::MenuItem("select all",BIND_FOR(GUI_ACTION_PAT_SELECT_ALL))) doSelectAll(); - ImGui::Separator(); - if (ImGui::MenuItem("note up",BIND_FOR(GUI_ACTION_PAT_NOTE_UP))) doTranspose(1); - if (ImGui::MenuItem("note down",BIND_FOR(GUI_ACTION_PAT_NOTE_DOWN))) doTranspose(-1); - if (ImGui::MenuItem("octave up",BIND_FOR(GUI_ACTION_PAT_OCTAVE_UP))) doTranspose(12); - if (ImGui::MenuItem("octave down",BIND_FOR(GUI_ACTION_PAT_OCTAVE_DOWN))) doTranspose(-12); + editOptions(true); /*ImGui::Separator(); ImGui::MenuItem("clear...");*/ ImGui::EndMenu(); diff --git a/src/gui/gui.h b/src/gui/gui.h index e64fe4a04..1ce74f3ea 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -233,6 +233,10 @@ enum FurnaceGUIActions { GUI_ACTION_PAT_CUT, GUI_ACTION_PAT_COPY, GUI_ACTION_PAT_PASTE, + GUI_ACTION_PAT_PASTE_MIX, + GUI_ACTION_PAT_PASTE_MIX_BG, + GUI_ACTION_PAT_PASTE_FLOOD, + GUI_ACTION_PAT_PASTE_OVERFLOW, GUI_ACTION_PAT_CURSOR_UP, GUI_ACTION_PAT_CURSOR_DOWN, GUI_ACTION_PAT_CURSOR_LEFT, @@ -268,6 +272,18 @@ enum FurnaceGUIActions { GUI_ACTION_PAT_COLLAPSE, GUI_ACTION_PAT_INCREASE_COLUMNS, GUI_ACTION_PAT_DECREASE_COLUMNS, + GUI_ACTION_PAT_INTERPOLATE, + GUI_ACTION_PAT_FADE_IN, + GUI_ACTION_PAT_FADE_OUT, + GUI_ACTION_PAT_INVERT_VALUES, + GUI_ACTION_PAT_FLIP_SELECTION, + GUI_ACTION_PAT_COLLAPSE_ROWS, + GUI_ACTION_PAT_EXPAND_ROWS, + GUI_ACTION_PAT_COLLAPSE_PAT, + GUI_ACTION_PAT_EXPAND_PAT, + GUI_ACTION_PAT_COLLAPSE_SONG, + GUI_ACTION_PAT_EXPAND_SONG, + GUI_ACTION_PAT_LATCH, GUI_ACTION_PAT_MAX, GUI_ACTION_INS_LIST_MIN, @@ -334,6 +350,14 @@ enum FurnaceGUIActions { GUI_ACTION_MAX }; +enum PasteMode { + GUI_PASTE_MODE_NORMAL=0, + GUI_PASTE_MODE_MIX_FG, + GUI_PASTE_MODE_MIX_BG, + GUI_PASTE_MODE_FLOOD, + GUI_PASTE_MODE_OVERFLOW +}; + #define FURKMOD_CTRL (1<<31) #define FURKMOD_SHIFT (1<<29) #define FURKMOD_META (1<<28) @@ -720,9 +744,10 @@ class FurnaceGUI { void doInsert(); void doTranspose(int amount); void doCopy(bool cut); - void doPaste(); + void doPaste(PasteMode mode=GUI_PASTE_MODE_NORMAL); void doUndo(); void doRedo(); + void editOptions(bool topMenu); void play(int row=0); void stop(); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 2b3f2d6a4..b2ff56cec 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -813,59 +813,7 @@ void FurnaceGUI::drawPattern() { if (patternOpen) { if (!inhibitMenu && ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::OpenPopup("patternActionMenu"); if (ImGui::BeginPopup("patternActionMenu",ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { - char id[4096]; - ImGui::Selectable("cut"); - ImGui::Selectable("copy"); - ImGui::Selectable("paste"); - if (ImGui::BeginMenu("paste special...")) { - ImGui::Selectable("paste mix"); - ImGui::Selectable("paste mix (background)"); - ImGui::Selectable("paste flood"); - ImGui::Selectable("paste overflow"); - ImGui::EndMenu(); - } - ImGui::Selectable("delete"); - ImGui::Separator(); - - ImGui::InputInt("##TransposeAmount",&transposeAmount,1,1); - ImGui::SameLine(); - ImGui::Button("Transpose"); - - ImGui::Separator(); - ImGui::Selectable("interpolate"); - ImGui::Selectable("fade in"); - ImGui::Selectable("fade out"); - if (ImGui::BeginMenu("change instrument...")) { - if (e->song.ins.empty()) { - ImGui::Text("no instruments available"); - } - for (size_t i=0; isong.ins.size(); i++) { - snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); - if (ImGui::Selectable(id)) { // TODO - } - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("scale...")) { - ImGui::InputFloat("Bottom",&scaleMin,1,1); - ImGui::InputFloat("Top",&scaleMax,1,1); - ImGui::Button("Scale"); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("randomize...")) { - ImGui::InputInt("Minimum",&randomizeMin,1,1); - ImGui::InputInt("Maximum",&randomizeMax,1,1); - ImGui::Button("Randomize"); - ImGui::EndMenu(); - } - ImGui::Selectable("invert values"); - - ImGui::SameLine(); - - ImGui::Selectable("flip selection"); - ImGui::Selectable("collapse"); - ImGui::Selectable("expand"); - + editOptions(false); ImGui::EndPopup(); } } From d0b76ed5a6bd6bfc319bb512a347bbd07ebcfc13 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 10 Mar 2022 17:36:27 +0100 Subject: [PATCH 039/105] Updated Lynx register sheet --- src/engine/platform/lynx.cpp | 39 +++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 2783ba5d5..872cd99c5 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -82,7 +82,44 @@ static int32_t clamp(int32_t v, int32_t lo, int32_t hi) } const char* regCheatSheetLynx[]={ - "DATA", "0", + "AUDIO0_VOLCNTRL", "20", + "AUDIO0_FEEDBACK", "21", + "AUDIO0_OUTPUT", "22", + "AUDIO0_SHIFT", "23", + "AUDIO0_BACKUP", "24", + "AUDIO0_CONTROL", "25", + "AUDIO0_COUNTER", "26", + "AUDIO0_OTHER", "27", + "AUDIO1_VOLCNTRL", "28", + "AUDIO1_FEEDBACK", "29", + "AUDIO1_OUTPUT", "2a", + "AUDIO1_SHIFT", "2b", + "AUDIO1_BACKUP", "2c", + "AUDIO1_CONTROL", "2d", + "AUDIO1_COUNTER", "2e", + "AUDIO1_OTHER", "2f", + "AUDIO2_VOLCNTRL", "30", + "AUDIO2_FEEDBACK", "31", + "AUDIO2_OUTPUT", "32", + "AUDIO2_SHIFT", "33", + "AUDIO2_BACKUP", "34", + "AUDIO2_CONTROL", "35", + "AUDIO2_COUNTER", "36", + "AUDIO2_OTHER", "37", + "AUDIO3_VOLCNTRL", "38", + "AUDIO3_FEEDBACK", "39", + "AUDIO3_OUTPUT", "3a", + "AUDIO3_SHIFT", "3b", + "AUDIO3_BACKUP", "3c", + "AUDIO3_CONTROL", "3d", + "AUDIO3_COUNTER", "3e", + "AUDIO3_OTHER", "3f", + "ATTENREG0", "40", + "ATTENREG1", "41", + "ATTENREG2", "42", + "ATTENREG3", "43", + "MPAN", "44", + "MSTEREO", "50", NULL }; From 8d447542e108feebe52af00a715020c9ff77dd52 Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 11 Mar 2022 02:42:27 +0900 Subject: [PATCH 040/105] Use lamda --- src/engine/platform/sound/x1_010/x1_010.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/sound/x1_010/x1_010.cpp b/src/engine/platform/sound/x1_010/x1_010.cpp index ea1e52ace..150928e66 100644 --- a/src/engine/platform/sound/x1_010/x1_010.cpp +++ b/src/engine/platform/sound/x1_010/x1_010.cpp @@ -215,8 +215,8 @@ void x1_010_core::voice_t::reset() void x1_010_core::reset() { - for (int i = 0; i < 16; i++) - m_voice[i].reset(); + for (auto & elem : m_voice) + elem.reset(); std::fill_n(&m_envelope[0], 0x1000, 0); std::fill_n(&m_wave[0], 0x1000, 0); From f3e4810dda918f0d0cb265fc3d8a0271596d69ba Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 11 Mar 2022 03:47:36 +0900 Subject: [PATCH 041/105] Make some pitch command work in ADPCM-B, still partially and 01xx command is broken Fix sample check routine Remove dac* variables, No way to enable DAC mode in YM2610* --- src/engine/platform/ym2610.cpp | 73 +++++++++++++++++++------------- src/engine/platform/ym2610.h | 8 ++-- src/engine/platform/ym2610b.cpp | 75 +++++++++++++++++++-------------- src/engine/platform/ym2610b.h | 8 ++-- 4 files changed, 93 insertions(+), 71 deletions(-) diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 1bd460fa1..5b2b19cc8 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -313,11 +313,22 @@ const char* DivPlatformYM2610::getEffectName(unsigned char effect) { return NULL; } +double DivPlatformYM2610::NOTE_OPNB(int ch, int note) { + if (ch>6) { // ADPCM + return NOTE_ADPCMB(note); + } else if (ch>3) { // PSG + return NOTE_PERIODIC(note); + } + // FM + return NOTE_FREQUENCY(note); +} + double DivPlatformYM2610::NOTE_ADPCMB(int note) { - DivInstrument* ins=parent->getIns(chan[13].ins); - if (ins->type!=DIV_INS_AMIGA) return 0; - double off=(double)(parent->getSample(ins->amiga.initSample)->centerRate)/8363.0; - return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); + if (chan[13].sample>=0&&chan[13].samplesong.sampleLen) { + double off=(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0; + return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); + } + return 0; } void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t len) { @@ -691,22 +702,33 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x1b,chan[c.chan].outVol); } - DivSample* s=parent->getSample(ins->amiga.initSample); - immWrite(0x12,(s->offB>>8)&0xff); - immWrite(0x13,s->offB>>16); - int end=s->offB+s->lengthB-1; - immWrite(0x14,(end>>8)&0xff); - immWrite(0x15,end>>16); - immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat - if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].note=c.value; - chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); - chan[c.chan].freqChanged=true; + chan[c.chan].sample=ins->amiga.initSample; + if (chan[c.chan].sample>=0&&chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + immWrite(0x12,(s->offB>>8)&0xff); + immWrite(0x13,s->offB>>16); + int end=s->offB+s->lengthB-1; + immWrite(0x14,(end>>8)&0xff); + immWrite(0x15,end>>16); + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + immWrite(0x10,0x01); // reset + immWrite(0x12,0); + immWrite(0x13,0); + immWrite(0x14,0); + immWrite(0x15,0); + break; } - chan[c.chan].active=true; - chan[c.chan].keyOn=true; } else { + chan[c.chan].sample=-1; chan[c.chan].std.init(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { @@ -915,8 +937,8 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (c.chan>3) { // PSG - int destFreq=NOTE_PERIODIC(c.value2); + if (c.chan>3) { // PSG, ADPCM-B + int destFreq=NOTE_OPNB(c.chan,c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -975,11 +997,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { - if (c.chan>3) { // PSG - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); - } else { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); - } + chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); chan[c.chan].freqChanged=true; break; } @@ -1210,11 +1228,6 @@ void DivPlatformYM2610::reset() { } lastBusy=60; - dacMode=0; - dacPeriod=0; - dacPos=0; - dacRate=0; - dacSample=-1; sampleBank=0; ayEnvPeriod=0; ayEnvMode=0; diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index a0ee8b8d4..4e9b81f98 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -47,6 +47,7 @@ class DivPlatformYM2610: public DivDispatch { signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM; int vol, outVol; + int sample; unsigned char pan; DivMacroInt std; Channel(): @@ -70,6 +71,7 @@ class DivPlatformYM2610: public DivDispatch { furnacePCM(false), vol(0), outVol(15), + sample(-1), pan(3) {} }; Channel chan[14]; @@ -87,11 +89,6 @@ class DivPlatformYM2610: public DivDispatch { unsigned char regPool[512]; unsigned char lastBusy; - bool dacMode; - int dacPeriod; - int dacRate; - int dacPos; - int dacSample; int ayNoiseFreq; unsigned char sampleBank; @@ -108,6 +105,7 @@ class DivPlatformYM2610: public DivDispatch { int octave(int freq); int toFreq(int freq); + double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index a6bcf9ac7..02d120331 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -377,11 +377,22 @@ const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { return NULL; } +double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { + if (ch>8) { // ADPCM-B + return NOTE_ADPCMB(note); + } else if (ch>5) { // PSG + return NOTE_PERIODIC(note); + } + // FM + return NOTE_FREQUENCY(note); +} + double DivPlatformYM2610B::NOTE_ADPCMB(int note) { - DivInstrument* ins=parent->getIns(chan[15].ins); - if (ins->type!=DIV_INS_AMIGA) return 0; - double off=(double)(parent->getSample(ins->amiga.initSample)->centerRate)/8363.0; - return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); + if (chan[15].sample>=0&&chan[15].samplesong.sampleLen) { + double off=(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); + } + return 0; } void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { @@ -752,24 +763,35 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].std.init(ins); if (!chan[c.chan].std.willVol) { chan[c.chan].outVol=chan[c.chan].vol; - immWrite(0x1b,chan[c.chan].outVol); + immWrite(0x1b,chan[c.chan].outVol); } - DivSample* s=parent->getSample(ins->amiga.initSample); - immWrite(0x12,(s->offB>>8)&0xff); - immWrite(0x13,s->offB>>16); - int end=s->offB+s->lengthB-1; - immWrite(0x14,(end>>8)&0xff); - immWrite(0x15,end>>16); - immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat - if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].note=c.value; - chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); - chan[c.chan].freqChanged=true; + chan[c.chan].sample=ins->amiga.initSample; + if (chan[c.chan].sample>=0&&chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + immWrite(0x12,(s->offB>>8)&0xff); + immWrite(0x13,s->offB>>16); + int end=s->offB+s->lengthB-1; + immWrite(0x14,(end>>8)&0xff); + immWrite(0x15,end>>16); + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + immWrite(0x10,0x01); // reset + immWrite(0x12,0); + immWrite(0x13,0); + immWrite(0x14,0); + immWrite(0x15,0); + break; } - chan[c.chan].active=true; - chan[c.chan].keyOn=true; } else { + chan[c.chan].sample=-1; chan[c.chan].std.init(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { @@ -978,8 +1000,8 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (c.chan>5) { // PSG - int destFreq=NOTE_PERIODIC(c.value2); + if (c.chan>5) { // PSG, ADPCM-B + int destFreq=NOTE_OPNB(c.chan,c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -1038,11 +1060,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { - if (c.chan>5) { // PSG - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); - } else { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); - } + chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); chan[c.chan].freqChanged=true; break; } @@ -1273,11 +1291,6 @@ void DivPlatformYM2610B::reset() { } lastBusy=60; - dacMode=0; - dacPeriod=0; - dacPos=0; - dacRate=0; - dacSample=-1; sampleBank=0; ayEnvPeriod=0; ayEnvMode=0; diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index ca43e36b4..99c641a6e 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -40,6 +40,7 @@ class DivPlatformYM2610B: public DivDispatch { signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM; int vol, outVol; + int sample; unsigned char pan; DivMacroInt std; Channel(): @@ -63,6 +64,7 @@ class DivPlatformYM2610B: public DivDispatch { furnacePCM(false), vol(0), outVol(15), + sample(-1), pan(3) {} }; Channel chan[16]; @@ -80,11 +82,6 @@ class DivPlatformYM2610B: public DivDispatch { unsigned char regPool[512]; unsigned char lastBusy; - bool dacMode; - int dacPeriod; - int dacRate; - int dacPos; - int dacSample; int ayNoiseFreq; unsigned char sampleBank; @@ -101,6 +98,7 @@ class DivPlatformYM2610B: public DivDispatch { int octave(int freq); int toFreq(int freq); + double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); From 28192b77bd3ee851ee6dcf7a7cdca9d9c06254f5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 13:52:04 -0500 Subject: [PATCH 042/105] fix big endian functions --- src/engine/safeReader.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index df3936655..2a0d7c3ba 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -92,9 +92,9 @@ short SafeReader::readS() { short SafeReader::readS_BE() { if (curSeek+1>len) throw EndOfFileException(this,len); - short ret=*(short*)(&buf[curSeek]); + unsigned short ret=*(unsigned short*)(&buf[curSeek]); curSeek+=2; - return (ret>>8)|((ret&0xff)<<8); + return (short)((ret>>8)|((ret&0xff)<<8)); } int SafeReader::readI() { @@ -112,9 +112,9 @@ int SafeReader::readI() { int SafeReader::readI_BE() { if (curSeek+4>len) throw EndOfFileException(this,len); - int ret=*(int*)(&buf[curSeek]); + unsigned int ret=*(unsigned int*)(&buf[curSeek]); curSeek+=4; - return (ret>>24)|((ret&0xff0000)>>8)|((ret&0xff00)<<8)|((ret&0xff)<<24); + return (int)((ret>>24)|((ret&0xff0000)>>8)|((ret&0xff00)<<8)|((ret&0xff)<<24)); } int64_t SafeReader::readL() { From 406faaeeea8d3a12ec4c9d4d78284ac20d34a340 Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 11 Mar 2022 04:07:11 +0900 Subject: [PATCH 043/105] Gamate Handheld game console by Taiwanese Bit Corporation that supports stereo headphone, with had a AY-3-8910 based sound. --- src/gui/gui.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index b3e21250a..b6a93a40f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6532,6 +6532,12 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Gamate", { + DIV_SYSTEM_AY8910, 64, 0, 73, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Computers"); From 36b336c7f49a257403a750ae1ecb625aa2884fde Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 11 Mar 2022 04:11:23 +0900 Subject: [PATCH 044/105] A && B --- src/engine/platform/ym2610.cpp | 4 ++-- src/engine/platform/ym2610b.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 5b2b19cc8..8d7626632 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -324,7 +324,7 @@ double DivPlatformYM2610::NOTE_OPNB(int ch, int note) { } double DivPlatformYM2610::NOTE_ADPCMB(int note) { - if (chan[13].sample>=0&&chan[13].samplesong.sampleLen) { + if (chan[13].sample>=0 && chan[13].samplesong.sampleLen) { double off=(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0; return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); } @@ -703,7 +703,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x1b,chan[c.chan].outVol); } 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); immWrite(0x12,(s->offB>>8)&0xff); immWrite(0x13,s->offB>>16); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 02d120331..1d954ebe6 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -388,7 +388,7 @@ double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { } double DivPlatformYM2610B::NOTE_ADPCMB(int note) { - if (chan[15].sample>=0&&chan[15].samplesong.sampleLen) { + if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { double off=(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); } @@ -766,7 +766,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x1b,chan[c.chan].outVol); } 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); immWrite(0x12,(s->offB>>8)&0xff); immWrite(0x13,s->offB>>16); From b42ceae1cbaec8fd70bf9a3300eefd516bf80c3f Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 11 Mar 2022 04:15:04 +0900 Subject: [PATCH 045/105] Code style --- src/engine/platform/x1_010.cpp | 56 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index e6b07d01b..42853855a 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -30,7 +30,7 @@ #define envFill(c,a) rWrite(0x800|(c<<7)|(a&0x7f),(chan[c].lvol<<4)|chan[c].rvol) #define envWrite(c,a,l,r) rWrite(0x800|(c<<7)|(a&0x7f),(((chan[c].lvol*(l))/15)<<4)|((chan[c].rvol*(r))/15)) -#define refreshControl(c) chWrite(c,0,chan[c].active?(chan[c].pcm?1:((chan[c].env.flag.envEnable&&chan[c].env.flag.envOneshot)?7:3)):0); +#define refreshControl(c) chWrite(c,0,chan[c].active?(chan[c].pcm?1:((chan[c].env.flag.envEnable && chan[c].env.flag.envOneshot)?7:3)):0); #define CHIP_FREQBASE 4194304 @@ -259,7 +259,7 @@ 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) { + 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,9 +304,9 @@ 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.envSplit||chan[ch].env.flag.envHinvR||chan[ch].env.flag.envVinvR||chan[ch].env.flag.envHinvL||chan[ch].env.flag.envVinvL) { // Stereo config + } 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 @@ -339,12 +339,12 @@ void DivPlatformX1_010::tick() { chan[i].std.next(); if (chan[i].std.hadVol) { signed char macroVol=((chan[i].vol&15)*MIN(chan[i].furnacePCM?64:15,chan[i].std.vol))/(chan[i].furnacePCM?64:15); - if ((!isMuted[i])&&(macroVol!=chan[i].outVol)) { + if ((!isMuted[i]) && (macroVol!=chan[i].outVol)) { chan[i].outVol=macroVol; 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) { @@ -355,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) { @@ -391,35 +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 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) { + if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } 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) { + 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) { + 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) { + if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } @@ -428,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; @@ -457,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; @@ -467,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; @@ -508,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 { @@ -519,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 @@ -582,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; @@ -618,7 +618,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { if (chan[c.chan].env.shape!=c.value) { chan[c.chan].env.shape=c.value; if (!chan[c.chan].pcm) { - if (chan[c.chan].env.flag.envEnable&&(!isMuted[c.chan])) { + if (chan[c.chan].env.flag.envEnable && (!isMuted[c.chan])) { chan[c.chan].envChanged=true; } chan[c.chan].keyOn=true; @@ -678,7 +678,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { 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; @@ -713,35 +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 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) { + if (!isMuted[c.chan] && !chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } 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) { + 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) { + 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) { + if (!isMuted[c.chan] && !chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } From 587fecd11d41777bc5b7fe119e903eeb77787a00 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 14:40:45 -0500 Subject: [PATCH 046/105] temporarily strip out emulation code --- src/engine/platform/vera.cpp | 80 ------------------------------------ 1 file changed, 80 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 3376b0a39..e58266dd6 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -55,86 +55,6 @@ const char* DivPlatformVERA::getEffectName(unsigned char effect) { } void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { - // taken from the official X16 emulator's source code, (c) 2020 Frank van den Hoef - const uint8_t volume_lut_psg[64]={0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 52, 56, 59, 63}; - const uint8_t volume_lut_pcm[16]={0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64}; - for (size_t pos=start; pos>1)^(noiseState>>2)^(noiseState>>4)^(noiseState>>15))&1); - for (int i=0; i<16; i++) { - unsigned freq=regPool[i*4+0] | (regPool[i*4+1] << 8); - unsigned old_accum=chan[i].accum; - unsigned new_accum=old_accum+freq; - int val=0x20; - if ((old_accum^new_accum)&0x10000) chan[i].noiseval=noiseOut; - new_accum&=0x1ffff; - chan[i].accum=new_accum; - switch (regPool[i*4+3]>>6) { - case 0: val=(new_accum>>10)>(regPool[i*4+3]&(unsigned)0x3f)?0:63; break; - case 1: val=(new_accum>>11); break; - case 2: val=(new_accum&0x10000)?(~(new_accum>>10)&0x3f):((new_accum>>10)&0x3f); break; - case 3: val=chan[i].noiseval; break; - } - val=(val-0x20)*volume_lut_psg[regPool[i*4+2]&0x3f]; - lout+=(regPool[i*4+2]&0x40)?val:0; - rout+=(regPool[i*4+2]&0x80)?val:0; - } - // PCM - // simple one-channel sample player, actual hardware is essentially a DAC - // with buffering - if (chan[16].pcm.sample>=0) { - chan[16].accum+=regPool[65]; - if (chan[16].accum>=128) { - DivSample* s=parent->getSample(chan[16].pcm.sample); - if (s->samples>0) { - int tmp_l=0; - int tmp_r=0; - if (!isMuted[16]) { - // TODO stereo samples once DivSample has a support for it - switch (s->depth) { - case 8: - tmp_l=s->data8[chan[16].pcm.pos]*256; - tmp_r=tmp_l; - regPool[64]|=0x20; // for register viewer purposes - break; - case 16: - tmp_l=s->data16[chan[16].pcm.pos]; - tmp_r=tmp_l; - regPool[64]&=~0x20; - break; - } - if (!(chan[16].pan&1)) tmp_l=0; - if (!(chan[16].pan&2)) tmp_r=0; - } - chan[16].pcm.out_l=tmp_l; - chan[16].pcm.out_r=tmp_r; - } else { - chan[16].pcm.sample=-1; - } - chan[16].accum&=0x7f; - chan[16].pcm.pos++; - if (chan[16].pcm.pos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { - chan[16].pcm.pos=s->loopStart; - } else { - chan[16].pcm.sample=-1; - } - } - } - } - int pcmvol=volume_lut_pcm[regPool[64]&0x0f]; - lout+=chan[16].pcm.out_l*pcmvol/64; - rout+=chan[16].pcm.out_r*pcmvol/64; - - bufL[pos]=(short)(lout/2); - bufR[pos]=(short)(rout/2); - } } void DivPlatformVERA::reset() { From 9bd15bd513fab980706383f00cab338c649388d6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 15:51:27 -0500 Subject: [PATCH 047/105] VERA: bring up actual emulation core --- CMakeLists.txt | 2 + src/engine/platform/sound/vera_pcm.c | 130 +++++++++++++++++++++++++++ src/engine/platform/sound/vera_pcm.h | 31 +++++++ src/engine/platform/sound/vera_psg.c | 98 ++++++++++++++++++++ src/engine/platform/sound/vera_psg.h | 27 ++++++ src/engine/platform/vera.cpp | 11 ++- src/engine/platform/vera.h | 7 +- 7 files changed, 303 insertions(+), 3 deletions(-) create mode 100644 src/engine/platform/sound/vera_pcm.c create mode 100644 src/engine/platform/sound/vera_pcm.h create mode 100644 src/engine/platform/sound/vera_psg.c create mode 100644 src/engine/platform/sound/vera_psg.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 487d8617c..e3dbdbbaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,6 +234,8 @@ src/engine/platform/sound/gb/apu.c src/engine/platform/sound/gb/timing.c src/engine/platform/sound/pce_psg.cpp src/engine/platform/sound/nes/apu.c +src/engine/platform/sound/vera_psg.c +src/engine/platform/sound/vera_pcm.c src/engine/platform/sound/c64/sid.cc src/engine/platform/sound/c64/voice.cc diff --git a/src/engine/platform/sound/vera_pcm.c b/src/engine/platform/sound/vera_pcm.c new file mode 100644 index 000000000..2a95ff04e --- /dev/null +++ b/src/engine/platform/sound/vera_pcm.c @@ -0,0 +1,130 @@ +// Commander X16 Emulator +// Copyright (c) 2020 Frank van den Hoef +// All rights reserved. License: 2-clause BSD + +#include "vera_pcm.h" +#include + +static uint8_t volume_lut[16] = {0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64}; + +static void +fifo_reset(struct VERA_PCM* pcm) +{ + pcm->fifo_wridx = 0; + pcm->fifo_rdidx = 0; + pcm->fifo_cnt = 0; +} + +void +pcm_reset(struct VERA_PCM* pcm) +{ + fifo_reset(pcm); + pcm->ctrl = 0; + pcm->rate = 0; + pcm->cur_l = 0; + pcm->cur_r = 0; + pcm->phase = 0; +} + +void +pcm_write_ctrl(struct VERA_PCM* pcm, uint8_t val) +{ + if (val & 0x80) { + fifo_reset(pcm); + } + + pcm->ctrl = val & 0x3F; +} + +uint8_t +pcm_read_ctrl(struct VERA_PCM* pcm) +{ + uint8_t result = pcm->ctrl; + if (pcm->fifo_cnt == sizeof(pcm->fifo)) { + result |= 0x80; + } + return result; +} + +void +pcm_write_rate(struct VERA_PCM* pcm, uint8_t val) +{ + pcm->rate = val; +} + +uint8_t +pcm_read_rate(struct VERA_PCM* pcm) +{ + return pcm->rate; +} + +void +pcm_write_fifo(struct VERA_PCM* pcm, uint8_t val) +{ + if (pcm->fifo_cnt < sizeof(pcm->fifo)) { + pcm->fifo[pcm->fifo_wridx++] = val; + if (pcm->fifo_wridx == sizeof(pcm->fifo)) { + pcm->fifo_wridx = 0; + } + pcm->fifo_cnt++; + } +} + +static uint8_t +read_fifo(struct VERA_PCM* pcm) +{ + if (pcm->fifo_cnt == 0) { + return 0; + } + uint8_t result = pcm->fifo[pcm->fifo_rdidx++]; + if (pcm->fifo_rdidx == sizeof(pcm->fifo)) { + pcm->fifo_rdidx = 0; + } + pcm->fifo_cnt--; + return result; +} + +bool +pcm_is_fifo_almost_empty(struct VERA_PCM* pcm) +{ + return pcm->fifo_cnt < 1024; +} + +void +pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_samples) +{ + while (num_samples--) { + uint8_t old_phase = pcm->phase; + pcm->phase += pcm->rate; + if ((old_phase & 0x80) != (pcm->phase & 0x80)) { + switch ((pcm->ctrl >> 4) & 3) { + case 0: { // mono 8-bit + pcm->cur_l = (int16_t)read_fifo(pcm) << 8; + pcm->cur_r = pcm->cur_l; + break; + } + case 1: { // stereo 8-bit + pcm->cur_l = read_fifo(pcm) << 8; + pcm->cur_r = read_fifo(pcm) << 8; + break; + } + case 2: { // mono 16-bit + pcm->cur_l = read_fifo(pcm); + pcm->cur_l |= read_fifo(pcm) << 8; + pcm->cur_r = pcm->cur_l; + break; + } + case 3: { // stereo 16-bit + pcm->cur_l = read_fifo(pcm); + pcm->cur_l |= read_fifo(pcm) << 8; + pcm->cur_r = read_fifo(pcm); + pcm->cur_r |= read_fifo(pcm) << 8; + break; + } + } + } + + *(buf_l++) = ((int)pcm->cur_l * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + *(buf_r++) = ((int)pcm->cur_r * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + } +} diff --git a/src/engine/platform/sound/vera_pcm.h b/src/engine/platform/sound/vera_pcm.h new file mode 100644 index 000000000..d9b600d0b --- /dev/null +++ b/src/engine/platform/sound/vera_pcm.h @@ -0,0 +1,31 @@ +// Commander X16 Emulator +// Copyright (c) 2020 Frank van den Hoef +// All rights reserved. License: 2-clause BSD + +#pragma once + +#include +#include + +struct VERA_PCM { + uint8_t fifo[4096 - 1]; // Actual hardware FIFO is 4kB, but you can only use 4095 bytes. + unsigned fifo_wridx; + unsigned fifo_rdidx; + unsigned fifo_cnt; + + uint8_t ctrl; + uint8_t rate; + + int16_t cur_l, cur_r; + uint8_t phase; +}; + + +void pcm_reset(struct VERA_PCM* pcm); +void pcm_write_ctrl(struct VERA_PCM* pcm, uint8_t val); +uint8_t pcm_read_ctrl(struct VERA_PCM* pcm); +void pcm_write_rate(struct VERA_PCM* pcm, uint8_t val); +uint8_t pcm_read_rate(struct VERA_PCM* pcm); +void pcm_write_fifo(struct VERA_PCM* pcm, uint8_t val); +void pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_samples); +bool pcm_is_fifo_almost_empty(struct VERA_PCM* pcm); diff --git a/src/engine/platform/sound/vera_psg.c b/src/engine/platform/sound/vera_psg.c new file mode 100644 index 000000000..92dac698c --- /dev/null +++ b/src/engine/platform/sound/vera_psg.c @@ -0,0 +1,98 @@ +// Commander X16 Emulator +// Copyright (c) 2020 Frank van den Hoef +// All rights reserved. License: 2-clause BSD + +#include "vera_psg.h" + +#include +#include + +enum waveform { + WF_PULSE = 0, + WF_SAWTOOTH, + WF_TRIANGLE, + WF_NOISE, +}; + +static uint8_t volume_lut[64] = {0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 52, 56, 59, 63}; + +void +psg_reset(struct VERA_PSG* psg) +{ + memset(psg->channels, 0, sizeof(psg->channels)); +} + +void +psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val) +{ + reg &= 0x3f; + + int ch = reg / 4; + int idx = reg & 3; + + switch (idx) { + case 0: psg->channels[ch].freq = (psg->channels[ch].freq & 0xFF00) | val; break; + case 1: psg->channels[ch].freq = (psg->channels[ch].freq & 0x00FF) | (val << 8); break; + case 2: { + psg->channels[ch].right = (val & 0x80) != 0; + psg->channels[ch].left = (val & 0x40) != 0; + psg->channels[ch].volume = volume_lut[val & 0x3F]; + break; + } + case 3: { + psg->channels[ch].pw = val & 0x3F; + psg->channels[ch].waveform = val >> 6; + break; + } + } +} + +static inline void +render(struct VERA_PSG* psg, int16_t *left, int16_t *right) +{ + int l = 0; + int r = 0; + + for (int i = 0; i < 16; i++) { + struct VERAChannel *ch = &psg->channels[i]; + + unsigned new_phase = (ch->phase + ch->freq) & 0x1FFFF; + if ((ch->phase & 0x10000) != (new_phase & 0x10000)) { + ch->noiseval = rand() & 63; + } + ch->phase = new_phase; + + uint8_t v = 0; + switch (ch->waveform) { + case WF_PULSE: v = (ch->phase >> 10) > ch->pw ? 0 : 63; break; + case WF_SAWTOOTH: v = ch->phase >> 11; break; + case WF_TRIANGLE: v = (ch->phase & 0x10000) ? (~(ch->phase >> 10) & 0x3F) : ((ch->phase >> 10) & 0x3F); break; + case WF_NOISE: v = ch->noiseval; break; + } + int8_t sv = (v ^ 0x20); + if (sv & 0x20) { + sv |= 0xC0; + } + + int val = (int)sv * (int)ch->volume; + + if (ch->left) { + l += val; + } + if (ch->right) { + r += val; + } + } + + *left = l; + *right = r; +} + +void +psg_render(struct VERA_PSG* psg, int16_t *buf, unsigned num_samples) +{ + while (num_samples--) { + render(psg, &buf[0], &buf[1]); + buf += 2; + } +} diff --git a/src/engine/platform/sound/vera_psg.h b/src/engine/platform/sound/vera_psg.h new file mode 100644 index 000000000..9f94bb578 --- /dev/null +++ b/src/engine/platform/sound/vera_psg.h @@ -0,0 +1,27 @@ +// Commander X16 Emulator +// Copyright (c) 2020 Frank van den Hoef +// All rights reserved. License: 2-clause BSD + +#pragma once + +#include +#include + +struct VERAChannel { + uint16_t freq; + uint8_t volume; + bool left, right; + uint8_t pw; + uint8_t waveform; + + unsigned phase; + uint8_t noiseval; +}; + +struct VERA_PSG { + struct VERAChannel channels[16]; +}; + +void psg_reset(struct VERA_PSG* psg); +void psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val); +void psg_render(struct VERA_PSG* psg, int16_t *buf, unsigned num_samples); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index e58266dd6..99c6517ea 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -22,6 +22,9 @@ #include #include +#include "sound/vera_psg.h" +#include "sound/vera_pcm.h" + #define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d);} #define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f)) #define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0)) @@ -69,8 +72,6 @@ void DivPlatformVERA::reset() { } chan[16].vol=15; chan[16].pan=3; - noiseState=1; - noiseOut=0; } int DivPlatformVERA::calcNoteFreq(int ch, int note) { @@ -321,6 +322,8 @@ int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int isMuted[i]=false; } parent=p; + psg=new struct VERA_PSG; + pcm=new struct VERA_PCM; dumpWrites=false; skipRegisterWrites=false; chipClock=25000000; @@ -329,5 +332,9 @@ int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int return 17; } +void DivPlatformVERA::quit() { + delete psg; + delete pcm; +} DivPlatformVERA::~DivPlatformVERA() { } diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index e5cb2ad9e..e1369301b 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -23,6 +23,9 @@ #include "../instrument.h" #include "../macroInt.h" +struct VERA_PSG; +struct VERA_PCM; + class DivPlatformVERA: public DivDispatch { protected: struct Channel { @@ -46,8 +49,9 @@ class DivPlatformVERA: public DivDispatch { }; Channel chan[17]; bool isMuted[17]; - unsigned noiseState, noiseOut; unsigned char regPool[66]; + struct VERA_PSG* psg; + struct VERA_PCM* pcm; int calcNoteFreq(int ch, int note); friend void putDispatchChan(void*,int,int); @@ -68,6 +72,7 @@ class DivPlatformVERA: public DivDispatch { const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); ~DivPlatformVERA(); }; #endif From 2f02e24a2f9185e001dd5531ab44edf5e1182f8a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 16:52:59 -0500 Subject: [PATCH 048/105] VERA: get rid of rand() and adapt code --- src/engine/platform/sound/vera_psg.c | 16 ++++++++++++---- src/engine/platform/sound/vera_psg.h | 3 ++- src/engine/platform/vera.cpp | 9 ++++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/sound/vera_psg.c b/src/engine/platform/sound/vera_psg.c index 92dac698c..f61bd26a2 100644 --- a/src/engine/platform/sound/vera_psg.c +++ b/src/engine/platform/sound/vera_psg.c @@ -20,6 +20,8 @@ void psg_reset(struct VERA_PSG* psg) { memset(psg->channels, 0, sizeof(psg->channels)); + psg->noiseState=1; + psg->noiseOut=0; } void @@ -52,13 +54,18 @@ render(struct VERA_PSG* psg, int16_t *left, int16_t *right) { int l = 0; int r = 0; + // TODO this is a currently speculated noise generation + // as the hardware and sources for it are not out in the public + // and the official emulator just uses rand() + psg->noiseOut=((psg->noiseOut<<1)|(psg->noiseState&1))&63; + psg->noiseState=(psg->noiseState<<1)|(((psg->noiseState>>1)^(psg->noiseState>>2)^(psg->noiseState>>4)^(psg->noiseState>>15))&1); for (int i = 0; i < 16; i++) { struct VERAChannel *ch = &psg->channels[i]; unsigned new_phase = (ch->phase + ch->freq) & 0x1FFFF; if ((ch->phase & 0x10000) != (new_phase & 0x10000)) { - ch->noiseval = rand() & 63; + ch->noiseval = psg->noiseOut; } ch->phase = new_phase; @@ -89,10 +96,11 @@ render(struct VERA_PSG* psg, int16_t *left, int16_t *right) } void -psg_render(struct VERA_PSG* psg, int16_t *buf, unsigned num_samples) +psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samples) { while (num_samples--) { - render(psg, &buf[0], &buf[1]); - buf += 2; + render(psg, bufL, bufR); + bufL++; + bufR++; } } diff --git a/src/engine/platform/sound/vera_psg.h b/src/engine/platform/sound/vera_psg.h index 9f94bb578..7a6a7f01d 100644 --- a/src/engine/platform/sound/vera_psg.h +++ b/src/engine/platform/sound/vera_psg.h @@ -19,9 +19,10 @@ struct VERAChannel { }; struct VERA_PSG { + unsigned int noiseState, noiseOut; struct VERAChannel channels[16]; }; void psg_reset(struct VERA_PSG* psg); void psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val); -void psg_render(struct VERA_PSG* psg, int16_t *buf, unsigned num_samples); +void psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samples); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 99c6517ea..f6fc22f70 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -22,10 +22,12 @@ #include #include -#include "sound/vera_psg.h" -#include "sound/vera_pcm.h" +extern "C" { + #include "sound/vera_psg.h" + #include "sound/vera_pcm.h" +} -#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d);} +#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d); psg_writereg(psg,((c)*4+(a)),(d));} #define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f)) #define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0)) #define rWriteFIFOVol(d) rWrite(16,0,(regPool[64]&(~0x3f))|((d)&0x3f)) @@ -58,6 +60,7 @@ const char* DivPlatformVERA::getEffectName(unsigned char effect) { } void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { + psg_render(psg,bufL+start,bufR+start,len); } void DivPlatformVERA::reset() { From 6d9befaf278a8d7aa3e02222b0e6a9009ae56a27 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 17:30:34 -0500 Subject: [PATCH 049/105] yay --- src/engine/platform/sound/vera_pcm.c | 7 +++++-- src/engine/platform/vera.cpp | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/sound/vera_pcm.c b/src/engine/platform/sound/vera_pcm.c index 2a95ff04e..857ba46e2 100644 --- a/src/engine/platform/sound/vera_pcm.c +++ b/src/engine/platform/sound/vera_pcm.c @@ -124,7 +124,10 @@ pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_sa } } - *(buf_l++) = ((int)pcm->cur_l * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; - *(buf_r++) = ((int)pcm->cur_r * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + *(buf_l) += ((int)pcm->cur_l * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + *(buf_r) += ((int)pcm->cur_r * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + + buf_l++; + buf_r++; } } diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index f6fc22f70..3546ac1fc 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -59,8 +59,10 @@ const char* DivPlatformVERA::getEffectName(unsigned char effect) { return NULL; } +// TODO: wire up PCM. void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { psg_render(psg,bufL+start,bufR+start,len); + pcm_render(pcm,bufL+start,bufR+start,len); } void DivPlatformVERA::reset() { From a9f80b841c8e569174789e8c48c599ec002a842a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 17:46:40 -0500 Subject: [PATCH 050/105] VERA: add ins color and reset --- src/engine/platform/sound/vera_pcm.c | 2 ++ src/engine/platform/vera.cpp | 2 ++ src/gui/gui.cpp | 5 +++++ src/gui/gui.h | 1 + src/gui/settings.cpp | 2 ++ 5 files changed, 12 insertions(+) diff --git a/src/engine/platform/sound/vera_pcm.c b/src/engine/platform/sound/vera_pcm.c index 857ba46e2..740f5c821 100644 --- a/src/engine/platform/sound/vera_pcm.c +++ b/src/engine/platform/sound/vera_pcm.c @@ -4,6 +4,7 @@ #include "vera_pcm.h" #include +#include static uint8_t volume_lut[16] = {0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64}; @@ -13,6 +14,7 @@ fifo_reset(struct VERA_PCM* pcm) pcm->fifo_wridx = 0; pcm->fifo_rdidx = 0; pcm->fifo_cnt = 0; + memset(pcm->fifo,0,sizeof(pcm->fifo)); } void diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 3546ac1fc..e63ac8bec 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -69,6 +69,8 @@ void DivPlatformVERA::reset() { for (int i=0; i<17; i++) { chan[i]=Channel(); } + psg_reset(psg); + pcm_reset(pcm); memset(regPool,0,66); for (int i=0; i<16; i++) { chan[i].vol=63; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 35597b65b..41d085022 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1190,6 +1190,10 @@ void FurnaceGUI::drawInsList() { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); break; + case DIV_INS_VERA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); + name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d\n",i,ins->name,i); + break; default: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d\n",i,ins->name,i); @@ -5669,6 +5673,7 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_INSTR_BEEPER,ImVec4(0.0f,1.0f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_SWAN,ImVec4(0.3f,0.5f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_MIKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_VERA,ImVec4(0.4f,0.6f,1.0f,1.0f)) GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f)); diff --git a/src/gui/gui.h b/src/gui/gui.h index 1ce74f3ea..07048cbde 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -73,6 +73,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_BEEPER, GUI_COLOR_INSTR_SWAN, GUI_COLOR_INSTR_MIKEY, + GUI_COLOR_INSTR_VERA, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_FM, GUI_COLOR_CHANNEL_PULSE, diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 3f0e532db..cd3608987 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -492,6 +492,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_BEEPER,"PC Beeper"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SWAN,"WonderSwan"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_MIKEY,"Lynx"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_VERA,"VERA"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); ImGui::TreePop(); } @@ -1159,6 +1160,7 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_INSTR_BEEPER); PUT_UI_COLOR(GUI_COLOR_INSTR_SWAN); PUT_UI_COLOR(GUI_COLOR_INSTR_MIKEY); + PUT_UI_COLOR(GUI_COLOR_INSTR_VERA); PUT_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN); PUT_UI_COLOR(GUI_COLOR_CHANNEL_FM); PUT_UI_COLOR(GUI_COLOR_CHANNEL_PULSE); From eb48a3d1082adc8857bfecaa339b49f5ac5e463f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 21:35:34 -0500 Subject: [PATCH 051/105] Revert "Create OPL3 docs (read Effect Commands section)" --- papers/doc/7-systems/opl3.md | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 papers/doc/7-systems/opl3.md diff --git a/papers/doc/7-systems/opl3.md b/papers/doc/7-systems/opl3.md deleted file mode 100644 index 79fa9df05..000000000 --- a/papers/doc/7-systems/opl3.md +++ /dev/null @@ -1,22 +0,0 @@ -# Yamaha OPL3/YMF262 - -The Yamaha OPL3/YMF262 was an FM sound chip developed by Yamaha (obviously) and released in 1994. - -The OPL3 saw most of its use in PC sound cards (such as the SoundBlaster 16 and the Pro AudioSpectrum). - -Unfortunately, developers who wanted to port their OPL/OPL2 music to the OPL3 were very lazy in doing so, so most of them ended up disreguarding the additions to the OPL3 entirely, and would use entirely the same MIDI driver and patches. - -## Sound capabilities - - - 18 channels 2-op of FM synthesis - - 8 unique waveforms which can be used on the carrier or the modulator - - A "split operators" mode that makes it so that the first and second operators are their own "voices" (each take the base pitch of the note to play and add the frequency multiplier's pitch to it) - - Hard panning (left, center, and right only) - - A rhythm mode where the last 3 voices are replaced with 5 drum channels - - A 4-op mode where 12 FM channels are combined to make 6 4-op FM channels - - A "tremolo" mode where AM (amplitude modulation) is applied, but unfortunately without any strength or speed controls. - - A built in vibrato mode which enables vibrato without a music driver doing it for it - - ADSR support on the carrier and/or modulator - -## Effect commands - - PLEASE DO NOT MERGE UNTIL I HAVE COMMITED THE EFFECT COMMANDS TO THIS PULL REQUEST. From 0700ba7e658baf126f347b6b9ba389d64e41935a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 22:10:52 -0500 Subject: [PATCH 052/105] GUI: start with the pattern view focused --- src/gui/gui.cpp | 8 ++++++++ src/gui/gui.h | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d9ee10cfe..bb7168edf 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5319,6 +5319,11 @@ bool FurnaceGUI::loop() { drawChannels(); drawRegView(); + if (firstFrame) { + firstFrame=false; + if (patternOpen) nextWindow=GUI_WINDOW_PATTERN; + } + if (ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) { //ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard; switch (curFileDialog) { @@ -6112,6 +6117,8 @@ bool FurnaceGUI::init() { oldPat[i]=new DivPattern; } + firstFrame=true; + #ifdef __APPLE__ SDL_RaiseWindow(sdlWin); #endif @@ -6238,6 +6245,7 @@ FurnaceGUI::FurnaceGUI(): demandScrollX(false), fancyPattern(false), wantPatName(false), + firstFrame(true), curWindow(GUI_WINDOW_NOTHING), nextWindow(GUI_WINDOW_NOTHING), nextDesc(NULL), diff --git a/src/gui/gui.h b/src/gui/gui.h index c2f7d58d3..129361c95 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -584,7 +584,7 @@ class FurnaceGUI { bool pianoOpen, notesOpen, channelsOpen, regViewOpen; SelectionPoint selStart, selEnd, cursor; bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders; - bool collapseWindow, demandScrollX, fancyPattern, wantPatName; + bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame; FurnaceGUIWindows curWindow, nextWindow; float peak[2]; float patChanX[DIV_MAX_CHANS+1]; From 8e61a0d314b03ad59cff5b86cdbef5299edb37a7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 23:49:44 -0500 Subject: [PATCH 053/105] better channel names --- src/engine/sysDef.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 883c9a617..70e27dae8 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -880,7 +880,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) { sys!=DIV_SYSTEM_YM2151); } -const char* chanNames[38][32]={ +const char* chanNames[40][32]={ {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3) @@ -893,7 +893,7 @@ const char* chanNames[38][32]={ {"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610 {"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610 (extended channel 2) {"PSG 1", "PSG 2", "PSG 3"}, // AY-3-8910 - {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, // Amiga/POKEY/Swan/Lynx + {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, // Amiga/POKEY/Lynx {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"}, // YM2151/YM2414 {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"}, // YM2612 {"Channel 1", "Channel 2"}, // TIA @@ -919,6 +919,8 @@ const char* chanNames[38][32]={ {"FM 1", "FM 2", "FM 3", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 4-op + drums {"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, // QSound {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3) + {"Wave", "Wave/PCM", "Wave", "Wave/Noise"}, // Swan + {"PSG 1", "PSG 2", "PSG 3", "PSG 4", "PSG 5", "PSG 6", "PSG 7", "PSG 8", "PSG 9", "PSG 10", "PSG 11", "PSG 12", "PSG 13", "PSG 14", "PSG 15", "PSG 16", "PCM"}, // VERA }; const char* chanShortNames[38][32]={ @@ -962,7 +964,7 @@ const char* chanShortNames[38][32]={ {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) }; -const int chanTypes[40][32]={ +const int chanTypes[41][32]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759 {0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3) @@ -999,10 +1001,11 @@ const int chanTypes[40][32]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 drums {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 4-op {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums - {3, 3, 3, 3}, //Lynx + {3, 3, 3, 3}, // Lynx {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, // X1-010 + {3, 4, 3, 2}, // Swan }; const DivInstrumentType chanPrefType[46][28]={ @@ -1105,10 +1108,12 @@ const char* DivEngine::getChannelName(int chan) { break; case DIV_SYSTEM_AMIGA: case DIV_SYSTEM_POKEY: - case DIV_SYSTEM_SWAN: case DIV_SYSTEM_LYNX: return chanNames[12][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_SWAN: + return chanNames[38][dispatchChanOfChan[chan]]; + break; case DIV_SYSTEM_YM2151: return chanNames[13][dispatchChanOfChan[chan]]; break; @@ -1191,7 +1196,7 @@ const char* DivEngine::getChannelName(int chan) { return chanNames[36][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_VERA: - return chanNames[0][dispatchChanOfChan[chan]]; + return chanNames[39][dispatchChanOfChan[chan]]; break; } return "??"; @@ -1389,7 +1394,6 @@ int DivEngine::getChannelType(int chan) { break; case DIV_SYSTEM_AMIGA: case DIV_SYSTEM_POKEY: - case DIV_SYSTEM_SWAN: return chanTypes[12][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_YM2151: @@ -1479,6 +1483,9 @@ int DivEngine::getChannelType(int chan) { case DIV_SYSTEM_X1_010: return chanTypes[39][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_SWAN: + return chanTypes[40][dispatchChanOfChan[chan]]; + break; } return 1; } From 74a23b3ec5373a4b2cfa7f70b914dbad28086e00 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 01:31:21 -0500 Subject: [PATCH 054/105] GUI: begin work on some of the new actions --- src/gui/gui.cpp | 158 ++++++++++++++++++++++++++++++++++++++++++++---- src/gui/gui.h | 19 +++++- 2 files changed, 164 insertions(+), 13 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index bb7168edf..e79029e37 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2583,6 +2583,16 @@ void FurnaceGUI::prepareUndo(ActionType action) { case GUI_UNDO_PATTERN_PUSH: case GUI_UNDO_PATTERN_CUT: case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE_IN: + case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: for (int i=0; igetTotalChannelCount(); i++) { e->song.pat[i].getPattern(e->song.orders.ord[i][order],false)->copyOn(oldPat[i]); } @@ -2624,6 +2634,16 @@ void FurnaceGUI::makeUndo(ActionType action) { case GUI_UNDO_PATTERN_PUSH: case GUI_UNDO_PATTERN_CUT: case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE_IN: + case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: for (int i=0; igetTotalChannelCount(); i++) { DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][order],false); for (int j=0; jsong.patLen; j++) { @@ -2813,6 +2833,7 @@ void FurnaceGUI::doTranspose(int amount) { origNote+=12; origOctave--; } + // FIX!!!!! TODO if (origOctave>7) { origNote=12; origOctave=7; @@ -2995,6 +3016,67 @@ void FurnaceGUI::doPaste(PasteMode mode) { makeUndo(GUI_UNDO_PATTERN_PASTE); } +void FurnaceGUI::doChangeIns(int ins) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_CHANGE_INS); + + int iCoarse=selStart.xCoarse; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) { + pat->data[j][2]=ins; + } + } + } + + makeUndo(GUI_UNDO_PATTERN_CHANGE_INS); +} + +void FurnaceGUI::doInterpolate() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_INTERPOLATE); + + makeUndo(GUI_UNDO_PATTERN_INTERPOLATE); +} + +void FurnaceGUI::doFade(bool fadeIn) { + finishSelection(); + prepareUndo(fadeIn?GUI_UNDO_PATTERN_FADE_IN:GUI_UNDO_PATTERN_FADE_OUT); + + makeUndo(fadeIn?GUI_UNDO_PATTERN_FADE_IN:GUI_UNDO_PATTERN_FADE_OUT); +} + +void FurnaceGUI::doInvertValues() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_INVERT_VAL); + + makeUndo(GUI_UNDO_PATTERN_INVERT_VAL); +} + +void FurnaceGUI::doFlip() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_FLIP); + + makeUndo(GUI_UNDO_PATTERN_FLIP); +} + +void FurnaceGUI::doCollapse(int divider) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_COLLAPSE); + + makeUndo(GUI_UNDO_PATTERN_COLLAPSE); +} + +void FurnaceGUI::doExpand(int multiplier) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_EXPAND); + + makeUndo(GUI_UNDO_PATTERN_EXPAND); +} + void FurnaceGUI::doUndo() { if (undoHist.empty()) return; UndoStep& us=undoHist.back(); @@ -3014,6 +3096,16 @@ void FurnaceGUI::doUndo() { case GUI_UNDO_PATTERN_PUSH: case GUI_UNDO_PATTERN_CUT: case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE_IN: + case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: for (UndoPatternData& i: us.pat) { DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.oldVal; @@ -3051,6 +3143,16 @@ void FurnaceGUI::doRedo() { case GUI_UNDO_PATTERN_PUSH: case GUI_UNDO_PATTERN_CUT: case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE_IN: + case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: for (UndoPatternData& i: us.pat) { DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.newVal; @@ -3527,6 +3629,37 @@ void FurnaceGUI::doAction(int what) { e->song.pat[cursor.xCoarse].effectRows--; if (e->song.pat[cursor.xCoarse].effectRows<1) e->song.pat[cursor.xCoarse].effectRows=1; break; + case GUI_ACTION_PAT_INTERPOLATE: + doInterpolate(); + break; + case GUI_ACTION_PAT_FADE_IN: + doFade(true); + break; + case GUI_ACTION_PAT_FADE_OUT: + doFade(false); + break; + case GUI_ACTION_PAT_INVERT_VALUES: + doInvertValues(); + break; + case GUI_ACTION_PAT_FLIP_SELECTION: + doFlip(); + break; + case GUI_ACTION_PAT_COLLAPSE_ROWS: + doCollapse(2); + break; + case GUI_ACTION_PAT_EXPAND_ROWS: + doExpand(2); + break; + case GUI_ACTION_PAT_COLLAPSE_PAT: // TODO + break; + case GUI_ACTION_PAT_EXPAND_PAT: // TODO + break; + case GUI_ACTION_PAT_COLLAPSE_SONG: // TODO + break; + case GUI_ACTION_PAT_EXPAND_SONG: // TODO + break; + case GUI_ACTION_PAT_LATCH: // TODO + break; case GUI_ACTION_INS_LIST_ADD: curIns=e->addInstrument(cursor.xCoarse); @@ -4420,10 +4553,10 @@ void FurnaceGUI::editOptions(bool topMenu) { if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_PAT_COPY))) doCopy(false); if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_PAT_PASTE))) doPaste(); if (ImGui::BeginMenu("paste special...")) { - ImGui::MenuItem("paste mix",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX)); - ImGui::MenuItem("paste mix (background)",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX_BG)); - ImGui::MenuItem("paste flood",BIND_FOR(GUI_ACTION_PAT_PASTE_FLOOD)); - ImGui::MenuItem("paste overflow",BIND_FOR(GUI_ACTION_PAT_PASTE_OVERFLOW)); + if (ImGui::MenuItem("paste mix",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX))) doPaste(GUI_PASTE_MODE_MIX_FG); + if (ImGui::MenuItem("paste mix (background)",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX_BG))) doPaste(GUI_PASTE_MODE_MIX_BG); + if (ImGui::MenuItem("paste flood",BIND_FOR(GUI_ACTION_PAT_PASTE_FLOOD))) doPaste(GUI_PASTE_MODE_FLOOD); + if (ImGui::MenuItem("paste overflow",BIND_FOR(GUI_ACTION_PAT_PASTE_OVERFLOW))) doPaste(GUI_PASTE_MODE_OVERFLOW); ImGui::EndMenu(); } if (ImGui::MenuItem("delete",BIND_FOR(GUI_ACTION_PAT_DELETE))) doDelete(); @@ -4452,16 +4585,17 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::Separator(); - ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE)); - ImGui::MenuItem("fade in",BIND_FOR(GUI_ACTION_PAT_FADE_IN)); - ImGui::MenuItem("fade out",BIND_FOR(GUI_ACTION_PAT_FADE_OUT)); + if (ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE))) doInterpolate(); + if (ImGui::MenuItem("fade in",BIND_FOR(GUI_ACTION_PAT_FADE_IN))) doFade(true); + if (ImGui::MenuItem("fade out",BIND_FOR(GUI_ACTION_PAT_FADE_OUT))) doFade(false); if (ImGui::BeginMenu("change instrument...")) { if (e->song.ins.empty()) { ImGui::Text("no instruments available"); } for (size_t i=0; isong.ins.size(); i++) { snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); - if (ImGui::MenuItem(id)) { // TODO + if (ImGui::MenuItem(id)) { + doChangeIns(i); } } ImGui::EndMenu(); @@ -4488,13 +4622,13 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::EndMenu(); } - ImGui::MenuItem("invert values",BIND_FOR(GUI_ACTION_PAT_INVERT_VALUES)); + if (ImGui::MenuItem("invert values",BIND_FOR(GUI_ACTION_PAT_INVERT_VALUES))) doInvertValues(); ImGui::Separator(); - ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION)); - ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS)); - ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS)); + if (ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip(); + if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2); + if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2); if (topMenu) { ImGui::Separator(); diff --git a/src/gui/gui.h b/src/gui/gui.h index 129361c95..17e240c8c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -380,7 +380,17 @@ enum ActionType { GUI_UNDO_PATTERN_PULL, GUI_UNDO_PATTERN_PUSH, GUI_UNDO_PATTERN_CUT, - GUI_UNDO_PATTERN_PASTE + GUI_UNDO_PATTERN_PASTE, + GUI_UNDO_PATTERN_CHANGE_INS, + GUI_UNDO_PATTERN_INTERPOLATE, + GUI_UNDO_PATTERN_FADE_IN, + GUI_UNDO_PATTERN_FADE_OUT, + GUI_UNDO_PATTERN_SCALE, + GUI_UNDO_PATTERN_RANDOMIZE, + GUI_UNDO_PATTERN_INVERT_VAL, + GUI_UNDO_PATTERN_FLIP, + GUI_UNDO_PATTERN_COLLAPSE, + GUI_UNDO_PATTERN_EXPAND }; struct UndoPatternData { @@ -747,6 +757,13 @@ class FurnaceGUI { void doTranspose(int amount); void doCopy(bool cut); void doPaste(PasteMode mode=GUI_PASTE_MODE_NORMAL); + void doChangeIns(int ins); + void doInterpolate(); + void doFade(bool fadeIn); + void doInvertValues(); + void doFlip(); + void doCollapse(int divider); + void doExpand(int multiplier); void doUndo(); void doRedo(); void editOptions(bool topMenu); From 3ac1dce3fe453b02050ea6aa80f0217e0659f08c Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 12 Mar 2022 03:30:54 +0900 Subject: [PATCH 055/105] Add AY-3-8914 support as configurable in AY-3-8910 Previous PR (https://github.com/tildearrow/furnace/pull/278) is closed due this, but archived for info. It's AY with 4 level envelope volume per channel and different register format. --- papers/doc/7-systems/ay8910.md | 2 ++ src/engine/platform/ay.cpp | 65 ++++++++++++++++++++++++++++++---- src/engine/platform/ay.h | 6 +++- src/engine/sysDef.cpp | 13 ++++--- src/gui/gui.cpp | 6 +++- 5 files changed, 79 insertions(+), 13 deletions(-) diff --git a/papers/doc/7-systems/ay8910.md b/papers/doc/7-systems/ay8910.md index 3dd1049f6..4d13a153a 100644 --- a/papers/doc/7-systems/ay8910.md +++ b/papers/doc/7-systems/ay8910.md @@ -4,6 +4,8 @@ this chip was used in several home computers (ZX Spectrum, MSX, Amstrad CPC, Ata the chip's powerful sound comes from the envelope... +AY-3-8914 variant was used in Intellivision, it's basically original AY with 4 level envelope volume per channel and different register format. + # effects - `20xx`: set channel mode. `xx` may be one of the following: diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 0777fca8b..526b3fbd2 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -24,7 +24,7 @@ #include #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} } #define CHIP_DIVIDER 8 @@ -48,8 +48,28 @@ const char* regCheatSheetAY[]={ NULL }; +const char* regCheatSheetAY8914[]={ + "FreqL_A", "0", + "FreqL_B", "1", + "FreqL_C", "2", + "FreqL_Env", "3", + "FreqH_A", "4", + "FreqH_B", "5", + "FreqH_C", "6", + "FreqH_Env", "7", + "Enable", "8", + "FreqNoise", "9", + "Control_Env", "A", + "Volume_A", "B", + "Volume_B", "C", + "Volume_C", "D", + "PortA", "E", + "PortB", "F", + NULL +}; + const char** DivPlatformAY8910::getRegisterSheet() { - return regCheatSheetAY; + return intellivision?regCheatSheetAY8914:regCheatSheetAY; } const char* DivPlatformAY8910::getEffectName(unsigned char effect) { @@ -92,8 +112,13 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l } while (!writes.empty()) { QueuedWrite w=writes.front(); - ay->address_w(w.addr); - ay->data_w(w.val); + if (intellivision) { + ay8914_device* ay8914=(ay8914_device*)ay; + ay8914->write(w.addr,w.val); + } else { + ay->address_w(w.addr); + ay->data_w(w.val); + } regPool[w.addr&0x0f]=w.val; writes.pop(); } @@ -125,6 +150,8 @@ void DivPlatformAY8910::tick() { if (chan[i].outVol<0) chan[i].outVol=0; if (isMuted[i]) { rWrite(0x08+i,0); + } else if (intellivision && (chan[i].psgMode&4)) { + rWrite(0x08+i,(chan[i].outVol&0xc)<<2); } else { rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); } @@ -151,6 +178,8 @@ void DivPlatformAY8910::tick() { chan[i].psgMode=(chan[i].std.wave+1)&7; if (isMuted[i]) { rWrite(0x08+i,0); + } else if (intellivision && (chan[i].psgMode&4)) { + rWrite(0x08+i,(chan[i].outVol&0xc)<<2); } else { rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); } @@ -242,6 +271,8 @@ int DivPlatformAY8910::dispatch(DivCommand c) { chan[c.chan].std.init(ins); if (isMuted[c.chan]) { rWrite(0x08+c.chan,0); + } else if (intellivision && (chan[c.chan].psgMode&4)) { + rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2); } else { rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); } @@ -264,7 +295,13 @@ int DivPlatformAY8910::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(0x08+c.chan,0); } else { - if (chan[c.chan].active) rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + if (chan[c.chan].active) { + if (intellivision && (chan[c.chan].psgMode&4)) { + rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2); + } else { + rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } + } } break; } @@ -317,7 +354,11 @@ int DivPlatformAY8910::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(0x08+c.chan,0); } else if (chan[c.chan].active) { - rWrite(0x08+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2)); + if (intellivision && (chan[c.chan].psgMode&4)) { + rWrite(0x08+c.chan,(chan[c.chan].outVol&0xc)<<2); + } else { + rWrite(0x08+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2)); + } } } break; @@ -334,6 +375,8 @@ int DivPlatformAY8910::dispatch(DivCommand c) { } if (isMuted[c.chan]) { rWrite(0x08+c.chan,0); + } else if (intellivision && (chan[c.chan].psgMode&4)) { + rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2); } else { rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); } @@ -383,6 +426,8 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (isMuted[ch]) { rWrite(0x08+ch,0); + } else if (intellivision && (chan[ch].psgMode&4)) { + rWrite(0x08+ch,(chan[ch].vol&0xc)<<2); } else { rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2)); } @@ -508,14 +553,22 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { case 1: ay=new ym2149_device(rate); sunsoft=false; + intellivision=false; break; case 2: ay=new sunsoft_5b_sound_device(rate); sunsoft=true; + intellivision=false; + break; + case 3: + ay=new ay8914_device(rate); + sunsoft=false; + intellivision=true; break; default: ay=new ay8910_device(rate); sunsoft=false; + intellivision=false; break; } ay->device_start(); diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index c22929900..41c3c404e 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -26,6 +26,10 @@ class DivPlatformAY8910: public DivDispatch { protected: + const unsigned char AY8914RegRemap[16]={ + 0,4,1,5,2,6,9,8,11,12,13,3,7,10,14,15 + }; + inline unsigned char regRemap(unsigned char reg) { return intellivision?AY8914RegRemap[reg&0x0f]:reg&0x0f; } struct Channel { unsigned char freqH, freqL; int freq, baseFreq, note, pitch; @@ -60,7 +64,7 @@ class DivPlatformAY8910: public DivDispatch { int delay; bool extMode; - bool stereo, sunsoft; + bool stereo, sunsoft, intellivision; short oldWrites[16]; short pendingWrites[16]; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 70e27dae8..5e57f6e36 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -419,10 +419,6 @@ const char* DivEngine::getSongSystemName() { return "Vectrex"; case 5: // AY-3-8910, 1MHz return "Amstrad CPC"; - case 6: // AY-3-8910, 0.somethingMhz - return "Intellivision"; - case 8: // AY-3-8910, 0.somethingMhz - return "Intellivision (PAL)"; case 0x10: // YM2149, 1.79MHz return "MSX"; @@ -432,7 +428,12 @@ const char* DivEngine::getSongSystemName() { return "Sunsoft 5B standalone"; case 0x28: // 5B PAL return "Sunsoft 5B standalone (PAL)"; - + + case 0x30: // AY-3-8914, 1.79MHz + return "Intellivision"; + case 0x33: // AY-3-8914, 2MHz + return "Intellivision (PAL)"; + default: if ((song.systemFlags[0]&0x30)==0x00) { return "AY-3-8910"; @@ -440,6 +441,8 @@ const char* DivEngine::getSongSystemName() { return "Yamaha YM2149"; } else if ((song.systemFlags[0]&0x30)==0x20) { return "Overclocked Sunsoft 5B"; + } else if ((song.systemFlags[0]&0x30)==0x30) { + return "Intellivision"; } } } else if (song.system[0]==DIV_SYSTEM_SMS) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e79029e37..850fbf742 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5116,6 +5116,10 @@ bool FurnaceGUI::loop() { e->setSysFlags(i,(flags&(~0x30))|32,restart); updateWindowTitle(); } + if (ImGui::RadioButton("AY-3-8914",(flags&0x30)==48)) { + e->setSysFlags(i,(flags&(~0x30))|48,restart); + updateWindowTitle(); + } } bool stereo=flags&0x40; ImGui::BeginDisabled((flags&0x30)==32); @@ -6684,7 +6688,7 @@ FurnaceGUI::FurnaceGUI(): )); cat.systems.push_back(FurnaceGUISysDef( "Mattel Intellivision", { - DIV_SYSTEM_AY8910, 64, 0, 6, + DIV_SYSTEM_AY8910, 64, 0, 48, 0 } )); From 5fadcf4891d9fcadc06d66dc4000aa648bece34c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 16:53:46 -0500 Subject: [PATCH 056/105] GUI: fix transpose octave range --- src/gui/gui.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e79029e37..1cc6165c0 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2833,10 +2833,13 @@ void FurnaceGUI::doTranspose(int amount) { origNote+=12; origOctave--; } - // FIX!!!!! TODO - if (origOctave>7) { - origNote=12; - origOctave=7; + if (origOctave==9 && origNote>11) { + origNote=11; + origOctave=9; + } + if (origOctave>9) { + origNote=11; + origOctave=9; } if (origOctave<-5) { origNote=1; From ab3884e5aa3357d4478f07d4dcd325a59e73d75a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 16:58:43 -0500 Subject: [PATCH 057/105] clamp wave data issue #267 --- src/engine/platform/gb.cpp | 8 ++++++-- src/engine/platform/pce.cpp | 5 ++++- src/engine/platform/swan.cpp | 8 ++++++-- src/engine/platform/x1_010.cpp | 5 ++++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 942032e32..ea6ad7719 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -97,8 +97,12 @@ void DivPlatformGB::updateWave() { if (wt->max<1 || wt->len<1) { rWrite(0x30+i,0); } else { - unsigned char nibble1=15-((wt->data[(i*2)*wt->len/32]*15)/wt->max); - unsigned char nibble2=15-((wt->data[(1+i*2)*wt->len/32]*15)/wt->max); + int nibble1=15-((wt->data[(i*2)*wt->len/32]*15)/wt->max); + int nibble2=15-((wt->data[(1+i*2)*wt->len/32]*15)/wt->max); + if (nibble1<0) nibble1=0; + if (nibble1>15) nibble1=15; + if (nibble2<0) nibble2=0; + if (nibble2>15) nibble2=15; rWrite(0x30+i,(nibble1<<4)|nibble2); } } diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 296eafc41..1af1741c5 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -138,7 +138,10 @@ void DivPlatformPCE::updateWave(int ch) { if (wt->max<1 || wt->len<1) { chWrite(ch,0x06,0); } else { - chWrite(ch,0x06,wt->data[i*wt->len/32]*31/wt->max); + int data=wt->data[i*wt->len/32]*31/wt->max; + if (data<0) data=0; + if (data>31) data=31; + chWrite(ch,0x06,data); } } if (chan[ch].active) { diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 219871804..de6236478 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -119,8 +119,12 @@ void DivPlatformSwan::updateWave(int ch) { } } else { for (int i=0; i<16; i++) { - unsigned char nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max; - unsigned char nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max; + int nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max; + int nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max; + if (nibble1<0) nibble1=0; + if (nibble1>15) nibble1=15; + if (nibble2<0) nibble2=0; + if (nibble2>15) nibble2=15; rWrite(addr+i,nibble1|(nibble2<<4)); } } diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 42853855a..1782deb9d 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -282,7 +282,10 @@ void DivPlatformX1_010::updateWave(int ch) { if (wt->max<1 || wt->len<1) { waveWrite(ch,i,0); } else { - waveWrite(ch,i,wt->data[i*wt->len/128]*255/wt->max); + int data=wt->data[i*wt->len/128]*255/wt->max; + if (data<0) data=0; + if (data>255) data=255; + waveWrite(ch,i,data); } } if (!chan[ch].pcm) { From cb3c4e23021de6179e9e7005c9b57f7c5ed82702 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 17:00:57 -0500 Subject: [PATCH 058/105] GUI: clamp waves to max value --- src/gui/insEdit.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index c8eec6763..e653b097f 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1885,6 +1885,7 @@ void FurnaceGUI::drawWaveEdit() { modified=true; } for (int i=0; ilen; i++) { + if (wave->data[i]>wave->max) wave->data[i]=wave->max; wavePreview[i]=wave->data[i]; } if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; From 07d15643c262524be780ba346fcb45cb7b2fbeb7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 17:56:10 -0500 Subject: [PATCH 059/105] GUI: implement paste flood --- src/gui/gui.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 1cc6165c0..657aa9e4a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3014,6 +3014,10 @@ void FurnaceGUI::doPaste(PasteMode mode) { break; } j++; + + if (mode==GUI_PASTE_MODE_FLOOD && i==data.size()-1) { + i=1; + } } makeUndo(GUI_UNDO_PATTERN_PASTE); From 155e602e61c57fa4e25f64bdcaecb78097740faf Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 12 Mar 2022 10:22:21 +0900 Subject: [PATCH 060/105] Fix X1-010 VGM logging Register/RAM offset is Big endian --- src/engine/vgmOps.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 069d5a2ef..2518b9faa 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -157,7 +157,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_X1_010: for (int i=0; i<16; i++) { w->writeC(0xc8); - w->writeS(baseAddr2S+(i<<3)); + w->writeS_BE(baseAddr2S+(i<<3)); w->writeC(0); } break; @@ -404,7 +404,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; case DIV_SYSTEM_X1_010: w->writeC(0xc8); - w->writeS(baseAddr2S|(write.addr&0x1fff)); + w->writeS_BE(baseAddr2S|(write.addr&0x1fff)); w->writeC(write.val); break; case DIV_SYSTEM_YM2610: From 2643d6b0ee3d4c69f5d9d9a4d82e4b971c89cabd Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 12 Mar 2022 11:32:19 +0900 Subject: [PATCH 061/105] Clamp X1-010 Envelope wave --- src/engine/platform/x1_010.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 1782deb9d..8d1338e14 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -323,9 +323,15 @@ void DivPlatformX1_010::updateEnvelope(int ch) { } 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 + if (lo<0) lo=0; + if (lo>15) lo=15; + if (ro<0) ro=0; + if (ro>15) ro=15; envWrite(ch,i,lo,ro); } else { int out=wt->data[i*wt->len/128]*15/wt->max; + if (out<0) out=0; + if (out>15) out=15; envWrite(ch,i,out,out); } } From 2a0aa19b2b1b9e329ee2bf732f5faae5aabb70d4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 22:11:33 -0500 Subject: [PATCH 062/105] fix broken DAC mode adds new compat flag --- papers/format.md | 4 +++- src/engine/engine.h | 4 ++-- src/engine/fileOps.cpp | 14 ++++++++++++-- src/engine/platform/genesis.cpp | 12 ++++++++++++ src/engine/song.h | 4 +++- src/gui/gui.cpp | 4 ++++ 6 files changed, 36 insertions(+), 6 deletions(-) diff --git a/papers/format.md b/papers/format.md index 7bc30abe0..882c5d8c9 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 64: Furnace dev64 - 63: Furnace dev63 - 62: Furnace dev62 - 61: Furnace dev61 @@ -203,7 +204,8 @@ size | description 1 | ignore duplicate slides (>=50) or reserved 1 | stop portamento on note off (>=62) or reserved 1 | continuous vibrato (>=62) or reserved - 4 | reserved + 1 | broken DAC mode (>=64) or reserved + 3 | reserved 4?? | pointers to instruments 4?? | pointers to wavetables 4?? | pointers to samples diff --git a/src/engine/engine.h b/src/engine/engine.h index d16a987cb..29d5a7795 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev63" -#define DIV_ENGINE_VERSION 63 +#define DIV_VERSION "dev64" +#define DIV_ENGINE_VERSION 64 enum DivStatusView { DIV_STATUS_NOTHING=0, diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 3a79cebed..9a927fdd2 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -140,6 +140,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.algMacroBehavior=false; ds.brokenShortcutSlides=false; ds.ignoreDuplicateSlides=true; + ds.brokenDACMode=true; // 1.1 compat flags if (ds.version>24) { @@ -799,6 +800,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<62) { ds.stopPortaOnNoteOff=true; } + if (ds.version<64) { + ds.brokenDACMode=false; + } ds.isDMF=false; reader.readS(); // reserved @@ -975,7 +979,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.readC(); reader.readC(); } - for (int i=0; i<4; i++) reader.readC(); + if (ds.version>=64) { + ds.brokenDACMode=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<3; i++) reader.readC(); } else { for (int i=0; i<20; i++) reader.readC(); } @@ -1417,7 +1426,8 @@ SafeWriter* DivEngine::saveFur() { w->writeC(song.ignoreDuplicateSlides); w->writeC(song.stopPortaOnNoteOff); w->writeC(song.continuousVibrato); - for (int i=0; i<4; i++) { + w->writeC(song.brokenDACMode); + for (int i=0; i<3; i++) { w->writeC(0); } diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 16c44ae5c..aefde827f 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -99,6 +99,9 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s dacPos=s->loopStart; } else { dacSample=-1; + if (parent->song.brokenDACMode) { + rWrite(0x2b,0); + } } } dacPeriod+=MAX(40,dacRate); @@ -163,6 +166,9 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si dacPos=s->loopStart; } else { dacSample=-1; + if (parent->song.brokenDACMode) { + rWrite(0x2b,0); + } } } dacPeriod+=MAX(40,dacRate); @@ -460,6 +466,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (dumpWrites) addWrite(0xffff0002,0); break; } else { + rWrite(0x2b,1<<7); if (dumpWrites) addWrite(0xffff0000,dacSample); } dacPos=0; @@ -477,6 +484,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (dumpWrites) addWrite(0xffff0002,0); break; } else { + rWrite(0x2b,1<<7); if (dumpWrites) addWrite(0xffff0000,dacSample); } dacPos=0; @@ -541,6 +549,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (c.chan==5) { dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); + if (parent->song.brokenDACMode) { + rWrite(0x2b,0); + break; + } } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; diff --git a/src/engine/song.h b/src/engine/song.h index 2eb4fe5d2..44b027320 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -295,6 +295,7 @@ struct DivSong { bool ignoreDuplicateSlides; bool stopPortaOnNoteOff; bool continuousVibrato; + bool brokenDACMode; DivOrders orders; std::vector ins; @@ -359,7 +360,8 @@ struct DivSong { brokenShortcutSlides(false), ignoreDuplicateSlides(false), stopPortaOnNoteOff(false), - continuousVibrato(false) { + continuousVibrato(false), + brokenDACMode(false) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 657aa9e4a..a1f176fa1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2115,6 +2115,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, vibrato will not be reset on a new note."); } + ImGui::Checkbox("Broken DAC mode",&e->song.brokenDACMode); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, the DAC in YM2612 will be disabled if there isn't any sample playing."); + } ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { From 2103f249fb747a89fe0ac1cabf4e53e9a8cfb087 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 22:33:22 -0500 Subject: [PATCH 063/105] C64: fix note/env release cutting note --- src/engine/playback.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index f96474a70..7bee3c411 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1064,8 +1064,11 @@ void DivEngine::nextRow() { for (int i=0; idata[curRow][0]==0 && pat->data[curRow][1]==0)) { - if (pat->data[curRow][0]!=100) { - if (!chan[i].legato) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); + if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) { + if (!chan[i].legato) { + dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); + //chan[i].cut=ticks; + } } } } From 716298c49cac4463cf1115e13d4404322404c2d2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 22:41:04 -0500 Subject: [PATCH 064/105] Genesis: now fix off not working on channel 6 --- src/engine/platform/genesis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index aefde827f..b3096e72a 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -551,7 +551,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (dumpWrites) addWrite(0xffff0002,0); if (parent->song.brokenDACMode) { rWrite(0x2b,0); - break; + if (dacMode) break; } } chan[c.chan].keyOff=true; From cf07e1861ef32c3bf93b96e6c4634b50e673ee30 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 23:01:18 -0500 Subject: [PATCH 065/105] add "auto-insert one tick gap" option --- papers/format.md | 4 +++- src/engine/engine.h | 4 ++-- src/engine/fileOps.cpp | 14 ++++++++++++-- src/engine/playback.cpp | 19 ++++++++++++++++++- src/engine/song.h | 4 +++- src/gui/gui.cpp | 6 +++++- 6 files changed, 43 insertions(+), 8 deletions(-) diff --git a/papers/format.md b/papers/format.md index 882c5d8c9..b04deac74 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 65: Furnace dev65 - 64: Furnace dev64 - 63: Furnace dev63 - 62: Furnace dev62 @@ -205,7 +206,8 @@ size | description 1 | stop portamento on note off (>=62) or reserved 1 | continuous vibrato (>=62) or reserved 1 | broken DAC mode (>=64) or reserved - 3 | reserved + 1 | one tick cut (>=65) or reserved + 2 | reserved 4?? | pointers to instruments 4?? | pointers to wavetables 4?? | pointers to samples diff --git a/src/engine/engine.h b/src/engine/engine.h index 29d5a7795..fc70c8730 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev64" -#define DIV_ENGINE_VERSION 64 +#define DIV_VERSION "dev65" +#define DIV_ENGINE_VERSION 65 enum DivStatusView { DIV_STATUS_NOTHING=0, diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 9a927fdd2..3c1a9d2a1 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -141,6 +141,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.brokenShortcutSlides=false; ds.ignoreDuplicateSlides=true; ds.brokenDACMode=true; + ds.oneTickCut=false; // 1.1 compat flags if (ds.version>24) { @@ -803,6 +804,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<64) { ds.brokenDACMode=false; } + if (ds.version<65) { + ds.oneTickCut=false; + } ds.isDMF=false; reader.readS(); // reserved @@ -984,7 +988,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<3; i++) reader.readC(); + if (ds.version>=65) { + ds.oneTickCut=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<2; i++) reader.readC(); } else { for (int i=0; i<20; i++) reader.readC(); } @@ -1427,7 +1436,8 @@ SafeWriter* DivEngine::saveFur() { w->writeC(song.stopPortaOnNoteOff); w->writeC(song.continuousVibrato); w->writeC(song.brokenDACMode); - for (int i=0; i<3; i++) { + w->writeC(song.oneTickCut); + for (int i=0; i<2; i++) { w->writeC(0); } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 7bee3c411..4626635b0 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1067,7 +1067,24 @@ void DivEngine::nextRow() { if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) { if (!chan[i].legato) { dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); - //chan[i].cut=ticks; + + if (song.oneTickCut) { + bool doPrepareCut=true; + + for (int j=0; jdata[curRow][4+(j<<1)]==0x03) { + doPrepareCut=false; + break; + } + if (pat->data[curRow][4+(j<<1)]==0xea) { + if (pat->data[curRow][5+(j<<1)]>0) { + doPrepareCut=false; + break; + } + } + } + if (doPrepareCut) chan[i].cut=ticks; + } } } } diff --git a/src/engine/song.h b/src/engine/song.h index 44b027320..5747aa511 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -296,6 +296,7 @@ struct DivSong { bool stopPortaOnNoteOff; bool continuousVibrato; bool brokenDACMode; + bool oneTickCut; DivOrders orders; std::vector ins; @@ -361,7 +362,8 @@ struct DivSong { ignoreDuplicateSlides(false), stopPortaOnNoteOff(false), continuousVibrato(false), - brokenDACMode(false) { + brokenDACMode(false), + oneTickCut(false) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index a1f176fa1..88c945bdd 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2069,7 +2069,7 @@ void FurnaceGUI::drawCompatFlags() { } if (!compatFlagsOpen) return; if (ImGui::Begin("Compatibility Flags",&compatFlagsOpen)) { - ImGui::TextWrapped("these flags are stored in the song when saving in .fur format, and are automatically enabled when saving in .dmf format."); + ImGui::TextWrapped("these flags are designed to provide better DefleMask/older Furnace compatibility."); ImGui::Checkbox("Limit slide range",&e->song.limitSlides); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves."); @@ -2119,6 +2119,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, the DAC in YM2612 will be disabled if there isn't any sample playing."); } + ImGui::Checkbox("Auto-insert one tick gap between notes",&e->song.oneTickCut); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, a one-tick note cut will be inserted between non-legato/non-portamento notes.\nthis simulates the behavior of some Amiga/SNES music engines."); + } ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { From fe9b379ca93e52a1d60904d9cd7371bc7755c3d0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 23:47:16 -0500 Subject: [PATCH 066/105] GUI: implement paste mix --- src/gui/gui.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 88c945bdd..6fd31c409 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2985,9 +2985,15 @@ void FurnaceGUI::doPaste(PasteMode mode) { note[2]=line[charPos++]; note[3]=0; - if (!decodeNote(note,pat->data[j][0],pat->data[j][1])) { - invalidData=true; - break; + if ((mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG) && strcmp(note,"...")==0) { + // do nothing. + } else { + if (mode!=GUI_PASTE_MODE_MIX_BG || (pat->data[j][0]==0 && pat->data[j][1]==0)) { + if (!decodeNote(note,pat->data[j][0],pat->data[j][1])) { + invalidData=true; + break; + } + } } } else { if (charPos>=line.size()) { @@ -3003,14 +3009,18 @@ void FurnaceGUI::doPaste(PasteMode mode) { note[2]=0; if (strcmp(note,"..")==0) { - pat->data[j][iFine+1]=-1; + if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG)) { + pat->data[j][iFine+1]=-1; + } } else { unsigned int val=0; if (sscanf(note,"%2X",&val)!=1) { invalidData=true; break; } - if (iFine<(3+e->song.pat[iCoarse].effectRows*2)) pat->data[j][iFine+1]=val; + if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) { + if (iFine<(3+e->song.pat[iCoarse].effectRows*2)) pat->data[j][iFine+1]=val; + } } } iFine++; From f52d919240f0915112469926ae187d7fa4577542 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 23:50:59 -0500 Subject: [PATCH 067/105] GUI: implement paste overflow --- src/gui/gui.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6fd31c409..d8e61f3b7 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3032,6 +3032,10 @@ void FurnaceGUI::doPaste(PasteMode mode) { break; } j++; + if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && ordsong.ordersLen-1) { + j=0; + ord++; + } if (mode==GUI_PASTE_MODE_FLOOD && i==data.size()-1) { i=1; From 3e890a391b975a02601062a6001a850563bcf8ce Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 02:13:15 -0500 Subject: [PATCH 068/105] GUI: operation mask this means transpose also works on non-note columns! --- src/gui/gui.cpp | 190 +++++++++++++++++++++++++++++++++++++++++++++--- src/gui/gui.h | 7 +- 2 files changed, 187 insertions(+), 10 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d8e61f3b7..2d3342fe8 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2718,6 +2718,19 @@ void FurnaceGUI::doSelectAll() { } } +#define maskOut(x) \ + if (x==0) { \ + if (!opMaskNote) continue; \ + } else if (x==1) { \ + if (!opMaskIns) continue; \ + } else if (x==2) { \ + if (!opMaskVol) continue; \ + } else if (((x)&1)==0) { \ + if (!opMaskEffectVal) continue; \ + } else if (((x)&1)==1) { \ + if (!opMaskEffect) continue; \ + } + void FurnaceGUI::doDelete() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_DELETE); @@ -2730,6 +2743,7 @@ void FurnaceGUI::doDelete() { if (!e->song.chanShow[iCoarse]) continue; DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][iFine]=0; @@ -2763,6 +2777,7 @@ void FurnaceGUI::doPullDelete() { if (!e->song.chanShow[iCoarse]) continue; DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.patLen; j++) { if (jsong.patLen-1) { if (iFine==0) { @@ -2795,6 +2810,7 @@ void FurnaceGUI::doInsert() { if (!e->song.chanShow[iCoarse]) continue; DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.patLen-1; j>=selStart.y; j--) { if (j==selStart.y) { if (iFine==0) { @@ -2827,6 +2843,7 @@ void FurnaceGUI::doTranspose(int amount) { if (!e->song.chanShow[iCoarse]) continue; DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; @@ -2856,6 +2873,16 @@ void FurnaceGUI::doTranspose(int amount) { pat->data[j][0]=origNote; pat->data[j][1]=(unsigned char)origOctave; } + } else { + int top=255; + if (iFine==1) { + if (e->song.ins.empty()) continue; + top=e->song.ins.size()-1; + } else if (iFine==2) { // volume + top=e->getMaxVolumeChan(iCoarse); + } + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=MIN(top,MAX(0,pat->data[j][iFine+1]+amount)); } } } @@ -2985,6 +3012,11 @@ void FurnaceGUI::doPaste(PasteMode mode) { note[2]=line[charPos++]; note[3]=0; + if (iFine==0 && !opMaskNote) { + iFine++; + continue; + } + if ((mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG) && strcmp(note,"...")==0) { // do nothing. } else { @@ -3008,6 +3040,28 @@ void FurnaceGUI::doPaste(PasteMode mode) { note[1]=line[charPos++]; note[2]=0; + if (iFine==1) { + if (!opMaskIns) { + iFine++; + continue; + } + } else if (iFine==2) { + if (!opMaskVol) { + iFine++; + continue; + } + } else if ((iFine&1)==0) { + if (!opMaskEffectVal) { + iFine++; + continue; + } + } else if ((iFine&1)==1) { + if (!opMaskEffect) { + iFine++; + continue; + } + } + if (strcmp(note,"..")==0) { if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG)) { pat->data[j][iFine+1]=-1; @@ -3071,6 +3125,7 @@ void FurnaceGUI::doInterpolate() { makeUndo(GUI_UNDO_PATTERN_INTERPOLATE); } +// huh?! void FurnaceGUI::doFade(bool fadeIn) { finishSelection(); prepareUndo(fadeIn?GUI_UNDO_PATTERN_FADE_IN:GUI_UNDO_PATTERN_FADE_OUT); @@ -3082,9 +3137,73 @@ void FurnaceGUI::doInvertValues() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_INVERT_VAL); + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + top=e->song.ins.size()-1; + } else if (iFine==2) { // volume + top=e->getMaxVolumeChan(iCoarse); + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=top-pat->data[j][iFine+1]; + } + } + } + iFine=0; + } + makeUndo(GUI_UNDO_PATTERN_INVERT_VAL); } +void FurnaceGUI::doScale(float top) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_SCALE); + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + absoluteTop=e->song.ins.size()-1; + } else if (iFine==2) { // volume + absoluteTop=e->getMaxVolumeChan(iCoarse); + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=MIN(absoluteTop,(double)pat->data[j][iFine+1]*(top/100.0f)); + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_SCALE); +} + +void FurnaceGUI::doRandomize(int bottom, int top) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_RANDOMIZE); + + makeUndo(GUI_UNDO_PATTERN_RANDOMIZE); +} + void FurnaceGUI::doFlip() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_FLIP); @@ -4594,6 +4713,44 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::Separator(); + ImGui::Text("operation mask"); + + ImGui::PushFont(patFont); + if (ImGui::BeginTable("opMaskTable",5,ImGuiTableFlags_Borders|ImGuiTableFlags_SizingFixedFit|ImGuiTableFlags_NoHostExtendX)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_ACTIVE]); + if (ImGui::Selectable(opMaskNote?"C-4##opMaskNote":"---##opMaskNote",opMaskNote,ImGuiSelectableFlags_DontClosePopups)) { + opMaskNote=!opMaskNote; + } + ImGui::PopStyleColor(); + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]); + if (ImGui::Selectable(opMaskIns?"01##opMaskIns":"--##opMaskIns",opMaskIns,ImGuiSelectableFlags_DontClosePopups)) { + opMaskIns=!opMaskIns; + } + ImGui::PopStyleColor(); + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_VOLUME_MAX]); + if (ImGui::Selectable(opMaskVol?"7F##opMaskVol":"--##opMaskVol",opMaskVol,ImGuiSelectableFlags_DontClosePopups)) { + opMaskVol=!opMaskVol; + } + ImGui::PopStyleColor(); + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH]); + if (ImGui::Selectable(opMaskEffect?"04##opMaskEffect":"--##opMaskEffect",opMaskEffect,ImGuiSelectableFlags_DontClosePopups)) { + opMaskEffect=!opMaskEffect; + } + ImGui::TableNextColumn(); + if (ImGui::Selectable(opMaskEffectVal?"72##opMaskEffectVal":"--##opMaskEffectVal",opMaskEffectVal,ImGuiSelectableFlags_DontClosePopups)) { + opMaskEffectVal=!opMaskEffectVal; + } + ImGui::PopStyleColor(); + ImGui::EndTable(); + } + ImGui::PopFont(); + + ImGui::Text("input latch"); if (ImGui::MenuItem("set latch",BIND_FOR(GUI_ACTION_PAT_LATCH))) { // TODO } @@ -4630,23 +4787,29 @@ void FurnaceGUI::editOptions(bool topMenu) { ImGui::EndMenu(); } if (ImGui::BeginMenu("scale...")) { - if (ImGui::InputFloat("Bottom",&scaleMin,1,1,"%.1f%%")) { - if (scaleMin<0.0f) scaleMin=0.0f; - if (scaleMin>100.0f) scaleMin=100.0f; - } - if (ImGui::InputFloat("Top",&scaleMax,1,1,"%.1f%%")) { + if (ImGui::InputFloat("##ScaleMax",&scaleMax,1,1,"%.1f%%")) { if (scaleMax<0.0f) scaleMax=0.0f; - if (scaleMax>100.0f) scaleMax=100.0f; + if (scaleMax>25600.0f) scaleMax=25600.0f; } if (ImGui::Button("Scale")) { + doScale(scaleMax); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("randomize...")) { - ImGui::InputInt("Minimum",&randomizeMin,1,1); - ImGui::InputInt("Maximum",&randomizeMax,1,1); + if (ImGui::InputInt("Minimum",&randomizeMin,1,1)) { + if (randomizeMin<0) randomizeMin=0; + if (randomizeMin>255) randomizeMin=255; + if (randomizeMin>randomizeMax) randomizeMin=randomizeMax; + } + if (ImGui::InputInt("Maximum",&randomizeMax,1,1)) { + if (randomizeMax<0) randomizeMax=0; + if (randomizeMax255) randomizeMax=255; + } if (ImGui::Button("Randomize")) { + doRandomize(randomizeMin,randomizeMax); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); @@ -6412,6 +6575,16 @@ FurnaceGUI::FurnaceGUI(): curWindow(GUI_WINDOW_NOTHING), nextWindow(GUI_WINDOW_NOTHING), nextDesc(NULL), + opMaskNote(true), + opMaskIns(true), + opMaskVol(true), + opMaskEffect(true), + opMaskEffectVal(true), + latchNote(-1), + latchIns(-1), + latchVol(-1), + latchEffect(-1), + latchEffectVal(-1), wavePreviewOn(false), wavePreviewKey((SDL_Scancode)0), wavePreviewNote(0), @@ -6456,7 +6629,6 @@ FurnaceGUI::FurnaceGUI(): transposeAmount(0), randomizeMin(0), randomizeMax(255), - scaleMin(0.0f), scaleMax(100.0f), oldOrdersLen(0) { diff --git a/src/gui/gui.h b/src/gui/gui.h index 17e240c8c..483066210 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -601,6 +601,9 @@ class FurnaceGUI { float patChanSlideY[DIV_MAX_CHANS+1]; const int* nextDesc; + bool opMaskNote, opMaskIns, opMaskVol, opMaskEffect, opMaskEffectVal; + short latchNote, latchIns, latchVol, latchEffect, latchEffectVal; + // bit 31: ctrl // bit 30: reserved for SDL scancode mask // bit 29: shift @@ -687,7 +690,7 @@ class FurnaceGUI { SelectionPoint sel1, sel2; int dummyRows, demandX; int transposeAmount, randomizeMin, randomizeMax; - float scaleMin, scaleMax; + float scaleMax; int oldOrdersLen; DivOrders oldOrders; @@ -761,6 +764,8 @@ class FurnaceGUI { void doInterpolate(); void doFade(bool fadeIn); void doInvertValues(); + void doScale(float top); + void doRandomize(int bottom, int top); void doFlip(); void doCollapse(int divider); void doExpand(int multiplier); From a6eec9f7c4296598050060fffa44fbe09c7630a5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 02:24:23 -0500 Subject: [PATCH 069/105] GUI: implement randomize --- src/gui/gui.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2d3342fe8..e48034947 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3201,6 +3201,34 @@ void FurnaceGUI::doRandomize(int bottom, int top) { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_RANDOMIZE); + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + absoluteTop=e->song.ins.size()-1; + } else if (iFine==2) { // volume + absoluteTop=e->getMaxVolumeChan(iCoarse); + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (top-bottom<=0) { + pat->data[j][iFine+1]=MIN(absoluteTop,bottom); + } else { + pat->data[j][iFine+1]=MIN(absoluteTop,bottom+(rand()%(top-bottom))); + } + } + } + } + iFine=0; + } + makeUndo(GUI_UNDO_PATTERN_RANDOMIZE); } From 1f058ac653721f5e83166e1b0f0ee2ff5453887d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 02:32:02 -0500 Subject: [PATCH 070/105] GUI: add move cursor by edit step on insert option --- src/gui/gui.cpp | 3 +++ src/gui/gui.h | 2 ++ src/gui/settings.cpp | 9 +++++++++ 3 files changed, 14 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e48034947..a6fedaa4f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3769,6 +3769,9 @@ void FurnaceGUI::doAction(int what) { break; case GUI_ACTION_PAT_INSERT: doInsert(); + if (settings.stepOnInsert) { + moveCursor(0,editStep,false); + } break; case GUI_ACTION_PAT_MUTE_CURSOR: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; diff --git a/src/gui/gui.h b/src/gui/gui.h index 483066210..65b3c34e8 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -535,6 +535,7 @@ class FurnaceGUI { int guiColorsBase; int avoidRaisingPattern; int insFocusesPattern; + int stepOnInsert; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -578,6 +579,7 @@ class FurnaceGUI { guiColorsBase(0), avoidRaisingPattern(0), insFocusesPattern(1), + stepOnInsert(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index f3ef2f0ea..2d018399b 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -157,6 +157,11 @@ void FurnaceGUI::drawSettings() { settings.stepOnDelete=stepOnDeleteB; } + bool stepOnInsertB=settings.stepOnInsert; + if (ImGui::Checkbox("Move cursor by edit step on insert (push)",&stepOnInsertB)) { + settings.stepOnInsert=stepOnInsertB; + } + bool allowEditDockingB=settings.allowEditDocking; if (ImGui::Checkbox("Allow docking editors",&allowEditDockingB)) { settings.allowEditDocking=allowEditDockingB; @@ -206,6 +211,7 @@ void FurnaceGUI::drawSettings() { if (ImGui::RadioButton("Move by Edit Step##cmk1",settings.scrollStep==1)) { settings.scrollStep=1; } + ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Audio")) { @@ -885,6 +891,7 @@ void FurnaceGUI::syncSettings() { settings.guiColorsBase=e->getConfInt("guiColorsBase",0); settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0); settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); + settings.stepOnInsert=e->getConfInt("stepOnInsert",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -922,6 +929,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.guiColorsBase,0,1); clampSetting(settings.avoidRaisingPattern,0,1); clampSetting(settings.insFocusesPattern,0,1); + clampSetting(settings.stepOnInsert,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1120,6 +1128,7 @@ void FurnaceGUI::commitSettings() { e->setConf("guiColorsBase",settings.guiColorsBase); e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern); e->setConf("insFocusesPattern",settings.insFocusesPattern); + e->setConf("stepOnInsert",settings.stepOnInsert); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From e82b1e6a67adaa91bcdbd65324cd0b70e94f0599 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 03:04:16 -0500 Subject: [PATCH 071/105] GUI: don't allow right-click menu movement --- src/gui/pattern.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index b2ff56cec..f2ff7e100 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -812,7 +812,7 @@ void FurnaceGUI::drawPattern() { ImGui::PopStyleVar(); if (patternOpen) { if (!inhibitMenu && ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::OpenPopup("patternActionMenu"); - if (ImGui::BeginPopup("patternActionMenu",ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { + if (ImGui::BeginPopup("patternActionMenu",ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { editOptions(false); ImGui::EndPopup(); } From de604bdf01a21397d38f6b793c29b4404c16f9c5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 03:04:34 -0500 Subject: [PATCH 072/105] GUI: add gradient/fade edit option --- src/gui/gui.cpp | 96 ++++++++++++++++++++++++++++++++++++++----------- src/gui/gui.h | 11 +++--- 2 files changed, 81 insertions(+), 26 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index a6fedaa4f..812428bfd 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2593,8 +2593,7 @@ void FurnaceGUI::prepareUndo(ActionType action) { case GUI_UNDO_PATTERN_PASTE: case GUI_UNDO_PATTERN_CHANGE_INS: case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE_IN: - case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_FADE: case GUI_UNDO_PATTERN_SCALE: case GUI_UNDO_PATTERN_RANDOMIZE: case GUI_UNDO_PATTERN_INVERT_VAL: @@ -2644,8 +2643,7 @@ void FurnaceGUI::makeUndo(ActionType action) { case GUI_UNDO_PATTERN_PASTE: case GUI_UNDO_PATTERN_CHANGE_INS: case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE_IN: - case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_FADE: case GUI_UNDO_PATTERN_SCALE: case GUI_UNDO_PATTERN_RANDOMIZE: case GUI_UNDO_PATTERN_INVERT_VAL: @@ -3125,12 +3123,43 @@ void FurnaceGUI::doInterpolate() { makeUndo(GUI_UNDO_PATTERN_INTERPOLATE); } -// huh?! -void FurnaceGUI::doFade(bool fadeIn) { +void FurnaceGUI::doFade(int p0, int p1, bool mode) { finishSelection(); - prepareUndo(fadeIn?GUI_UNDO_PATTERN_FADE_IN:GUI_UNDO_PATTERN_FADE_OUT); + prepareUndo(GUI_UNDO_PATTERN_FADE); - makeUndo(fadeIn?GUI_UNDO_PATTERN_FADE_IN:GUI_UNDO_PATTERN_FADE_OUT); + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + absoluteTop=e->song.ins.size()-1; + } else if (iFine==2) { // volume + absoluteTop=e->getMaxVolumeChan(iCoarse); + } + if (selEnd.y-selStart.y<1) continue; + for (int j=selStart.y; j<=selEnd.y; j++) { + double fraction=double(j-selStart.y)/double(selEnd.y-selStart.y); + int value=p0+double(p1-p0)*fraction; + if (mode) { // nibble + value&=15; + pat->data[j][iFine+1]=MIN(absoluteTop,value|(value<<4)); + } else { // byte + pat->data[j][iFine+1]=MIN(absoluteTop,value); + } + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_FADE); } void FurnaceGUI::doInvertValues() { @@ -3274,8 +3303,7 @@ void FurnaceGUI::doUndo() { case GUI_UNDO_PATTERN_PASTE: case GUI_UNDO_PATTERN_CHANGE_INS: case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE_IN: - case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_FADE: case GUI_UNDO_PATTERN_SCALE: case GUI_UNDO_PATTERN_RANDOMIZE: case GUI_UNDO_PATTERN_INVERT_VAL: @@ -3321,8 +3349,7 @@ void FurnaceGUI::doRedo() { case GUI_UNDO_PATTERN_PASTE: case GUI_UNDO_PATTERN_CHANGE_INS: case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE_IN: - case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_FADE: case GUI_UNDO_PATTERN_SCALE: case GUI_UNDO_PATTERN_RANDOMIZE: case GUI_UNDO_PATTERN_INVERT_VAL: @@ -3811,12 +3838,6 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_PAT_INTERPOLATE: doInterpolate(); break; - case GUI_ACTION_PAT_FADE_IN: - doFade(true); - break; - case GUI_ACTION_PAT_FADE_OUT: - doFade(false); - break; case GUI_ACTION_PAT_INVERT_VALUES: doInvertValues(); break; @@ -4745,6 +4766,7 @@ void FurnaceGUI::editOptions(bool topMenu) { ImGui::Separator(); ImGui::Text("operation mask"); + ImGui::SameLine(); ImGui::PushFont(patFont); if (ImGui::BeginTable("opMaskTable",5,ImGuiTableFlags_Borders|ImGuiTableFlags_SizingFixedFit|ImGuiTableFlags_NoHostExtendX)) { @@ -4803,8 +4825,6 @@ void FurnaceGUI::editOptions(bool topMenu) { ImGui::Separator(); if (ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE))) doInterpolate(); - if (ImGui::MenuItem("fade in",BIND_FOR(GUI_ACTION_PAT_FADE_IN))) doFade(true); - if (ImGui::MenuItem("fade out",BIND_FOR(GUI_ACTION_PAT_FADE_OUT))) doFade(false); if (ImGui::BeginMenu("change instrument...")) { if (e->song.ins.empty()) { ImGui::Text("no instruments available"); @@ -4817,6 +4837,38 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::EndMenu(); } + if (ImGui::BeginMenu("gradient/fade...")) { + if (ImGui::InputInt("Start",&fadeMin,1,1)) { + if (fadeMin<0) fadeMin=0; + if (fadeMode) { + if (fadeMin>15) fadeMin=15; + } else { + if (fadeMin>255) fadeMin=255; + } + } + if (ImGui::InputInt("End",&fadeMax,1,1)) { + if (fadeMax<0) fadeMax=0; + if (fadeMode) { + if (fadeMax>15) fadeMax=15; + } else { + if (fadeMax>255) fadeMax=255; + } + } + if (ImGui::Checkbox("Nibble mode",&fadeMode)) { + if (fadeMode) { + if (fadeMin>15) fadeMin=15; + if (fadeMax>15) fadeMax=15; + } else { + if (fadeMin>255) fadeMin=255; + if (fadeMax>255) fadeMax=255; + } + } + if (ImGui::Button("Go ahead")) { + doFade(fadeMin,fadeMax,fadeMode); + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } if (ImGui::BeginMenu("scale...")) { if (ImGui::InputFloat("##ScaleMax",&scaleMax,1,1,"%.1f%%")) { if (scaleMax<0.0f) scaleMax=0.0f; @@ -4839,6 +4891,7 @@ void FurnaceGUI::editOptions(bool topMenu) { if (randomizeMax255) randomizeMax=255; } + // TODO: add an option to set effect to specific value? if (ImGui::Button("Randomize")) { doRandomize(randomizeMin,randomizeMax); ImGui::CloseCurrentPopup(); @@ -6660,7 +6713,10 @@ FurnaceGUI::FurnaceGUI(): transposeAmount(0), randomizeMin(0), randomizeMax(255), + fadeMin(0), + fadeMax(255), scaleMax(100.0f), + fadeMode(false), oldOrdersLen(0) { // octave 1 diff --git a/src/gui/gui.h b/src/gui/gui.h index 65b3c34e8..16312423a 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -275,8 +275,7 @@ enum FurnaceGUIActions { GUI_ACTION_PAT_INCREASE_COLUMNS, GUI_ACTION_PAT_DECREASE_COLUMNS, GUI_ACTION_PAT_INTERPOLATE, - GUI_ACTION_PAT_FADE_IN, - GUI_ACTION_PAT_FADE_OUT, + GUI_ACTION_PAT_FADE, GUI_ACTION_PAT_INVERT_VALUES, GUI_ACTION_PAT_FLIP_SELECTION, GUI_ACTION_PAT_COLLAPSE_ROWS, @@ -383,8 +382,7 @@ enum ActionType { GUI_UNDO_PATTERN_PASTE, GUI_UNDO_PATTERN_CHANGE_INS, GUI_UNDO_PATTERN_INTERPOLATE, - GUI_UNDO_PATTERN_FADE_IN, - GUI_UNDO_PATTERN_FADE_OUT, + GUI_UNDO_PATTERN_FADE, GUI_UNDO_PATTERN_SCALE, GUI_UNDO_PATTERN_RANDOMIZE, GUI_UNDO_PATTERN_INVERT_VAL, @@ -691,8 +689,9 @@ class FurnaceGUI { ImVec2 threeChars, twoChars; SelectionPoint sel1, sel2; int dummyRows, demandX; - int transposeAmount, randomizeMin, randomizeMax; + int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax; float scaleMax; + bool fadeMode; int oldOrdersLen; DivOrders oldOrders; @@ -764,7 +763,7 @@ class FurnaceGUI { void doPaste(PasteMode mode=GUI_PASTE_MODE_NORMAL); void doChangeIns(int ins); void doInterpolate(); - void doFade(bool fadeIn); + void doFade(int p0, int p1, bool mode); void doInvertValues(); void doScale(float top); void doRandomize(int bottom, int top); From 7971b7323b4d37d831db929f3b195b26ec75aaa3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 03:40:56 -0500 Subject: [PATCH 073/105] GUI: add nibble mode to randomize --- src/gui/gui.cpp | 41 +++++++++++++++++++++++++++++++++++------ src/gui/gui.h | 4 ++-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 812428bfd..4da41cb35 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3226,7 +3226,7 @@ void FurnaceGUI::doScale(float top) { makeUndo(GUI_UNDO_PATTERN_SCALE); } -void FurnaceGUI::doRandomize(int bottom, int top) { +void FurnaceGUI::doRandomize(int bottom, int top, bool mode) { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_RANDOMIZE); @@ -3247,10 +3247,21 @@ void FurnaceGUI::doRandomize(int bottom, int top) { absoluteTop=e->getMaxVolumeChan(iCoarse); } for (int j=selStart.y; j<=selEnd.y; j++) { + int value=0; + int value2=0; if (top-bottom<=0) { - pat->data[j][iFine+1]=MIN(absoluteTop,bottom); + value=MIN(absoluteTop,bottom); + value2=MIN(absoluteTop,bottom); } else { - pat->data[j][iFine+1]=MIN(absoluteTop,bottom+(rand()%(top-bottom))); + value=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); + value2=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); + } + if (mode) { + value&=15; + value2&=15; + pat->data[j][iFine+1]=value|(value2<<4); + } else { + pat->data[j][iFine+1]=value; } } } @@ -4883,17 +4894,34 @@ void FurnaceGUI::editOptions(bool topMenu) { if (ImGui::BeginMenu("randomize...")) { if (ImGui::InputInt("Minimum",&randomizeMin,1,1)) { if (randomizeMin<0) randomizeMin=0; - if (randomizeMin>255) randomizeMin=255; + if (randomMode) { + if (randomizeMin>15) randomizeMin=15; + } else { + if (randomizeMin>255) randomizeMin=255; + } if (randomizeMin>randomizeMax) randomizeMin=randomizeMax; } if (ImGui::InputInt("Maximum",&randomizeMax,1,1)) { if (randomizeMax<0) randomizeMax=0; if (randomizeMax255) randomizeMax=255; + if (randomMode) { + if (randomizeMax>15) randomizeMax=15; + } else { + if (randomizeMax>255) randomizeMax=255; + } + } + if (ImGui::Checkbox("Nibble mode",&randomMode)) { + if (randomMode) { + if (randomizeMin>15) randomizeMin=15; + if (randomizeMax>15) randomizeMax=15; + } else { + if (randomizeMin>255) randomizeMin=255; + if (randomizeMax>255) randomizeMax=255; + } } // TODO: add an option to set effect to specific value? if (ImGui::Button("Randomize")) { - doRandomize(randomizeMin,randomizeMax); + doRandomize(randomizeMin,randomizeMax,randomMode); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); @@ -6717,6 +6745,7 @@ FurnaceGUI::FurnaceGUI(): fadeMax(255), scaleMax(100.0f), fadeMode(false), + randomMode(false), oldOrdersLen(0) { // octave 1 diff --git a/src/gui/gui.h b/src/gui/gui.h index 16312423a..e1e414f83 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -691,7 +691,7 @@ class FurnaceGUI { int dummyRows, demandX; int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax; float scaleMax; - bool fadeMode; + bool fadeMode, randomMode; int oldOrdersLen; DivOrders oldOrders; @@ -766,7 +766,7 @@ class FurnaceGUI { void doFade(int p0, int p1, bool mode); void doInvertValues(); void doScale(float top); - void doRandomize(int bottom, int top); + void doRandomize(int bottom, int top, bool mode); void doFlip(); void doCollapse(int divider); void doExpand(int multiplier); From e775703c445cd8d0b4bb9abe6434ec2ba0a58aff Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Sat, 12 Mar 2022 12:16:01 +0100 Subject: [PATCH 074/105] Lynx panning swap --- src/engine/platform/lynx.cpp | 2 +- src/engine/platform/sound/lynx/Mikey.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 872cd99c5..dc0df8a65 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -235,7 +235,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { } break; case DIV_CMD_PANNING: - chan[c.chan].pan=((c.value&0x0f)<<4)|((c.value&0xf0)>>4); + chan[c.chan].pan=c.value; WRITE_ATTEN(c.chan,chan[c.chan].pan); break; case DIV_CMD_GET_VOLUME: diff --git a/src/engine/platform/sound/lynx/Mikey.cpp b/src/engine/platform/sound/lynx/Mikey.cpp index ad112baf1..5d12bbb86 100644 --- a/src/engine/platform/sound/lynx/Mikey.cpp +++ b/src/engine/platform/sound/lynx/Mikey.cpp @@ -435,8 +435,8 @@ public: case ATTENREG2: case ATTENREG3: mRegisterPool[8*4+idx] = value; - mAttenuationLeft[idx] = ( value & 0x0f ) << 2; - mAttenuationRight[idx] = ( value & 0xf0 ) >> 2; + mAttenuationRight[idx] = ( value & 0x0f ) << 2; + mAttenuationLeft[idx] = ( value & 0xf0 ) >> 2; break; case MPAN: mPan = value; From c778251f262b361dfaafc15e880495633f41e279 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 12 Mar 2022 23:39:38 +0900 Subject: [PATCH 075/105] Oops! It's already exists --- src/engine/vgmOps.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 2518b9faa..6133fff62 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -509,7 +509,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { int hasOPM=0; int hasSegaPCM=0; int segaPCMOffset=0xf8000d; - int hasX1010=0; int hasRFC=0; int hasOPN=0; int hasOPNA=0; @@ -666,14 +665,14 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { } break; case DIV_SYSTEM_X1_010: - if (!hasX1010) { - hasX1010=disCont[i].dispatch->chipClock; + if (!hasX1) { + hasX1=disCont[i].dispatch->chipClock; willExport[i]=true; writeX1010=true; - } else if (!(hasX1010&0x40000000)) { + } else if (!(hasX1&0x40000000)) { isSecond[i]=true; willExport[i]=true; - hasX1010|=0x40000000; + hasX1|=0x40000000; howManyChips++; } break; From ab8bace7f4e04496ad0693c05b42e30cdfb9a3be Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 12:53:53 -0500 Subject: [PATCH 076/105] change default SAA1099 core to SAASound --- src/engine/dispatchContainer.cpp | 2 +- src/gui/gui.h | 2 +- src/gui/settings.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index da37565b9..ca2d99ce2 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -239,7 +239,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformOPL*)dispatch)->setOPLType(3,true); break; case DIV_SYSTEM_SAA1099: { - int saaCore=eng->getConfInt("saaCore",0); + int saaCore=eng->getConfInt("saaCore",1); if (saaCore<0 || saaCore>2) saaCore=0; dispatch=new DivPlatformSAA1099; ((DivPlatformSAA1099*)dispatch)->setCore((DivSAACores)saaCore); diff --git a/src/gui/gui.h b/src/gui/gui.h index e1e414f83..0aec32566 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -547,7 +547,7 @@ class FurnaceGUI { audioQuality(0), arcadeCore(0), ym2612Core(0), - saaCore(0), + saaCore(1), mainFont(0), patFont(0), audioRate(44100), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 2d018399b..c8f6763b4 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -861,7 +861,7 @@ void FurnaceGUI::syncSettings() { settings.audioRate=e->getConfInt("audioRate",44100); settings.arcadeCore=e->getConfInt("arcadeCore",0); settings.ym2612Core=e->getConfInt("ym2612Core",0); - settings.saaCore=e->getConfInt("saaCore",0); + settings.saaCore=e->getConfInt("saaCore",1); settings.mainFont=e->getConfInt("mainFont",0); settings.patFont=e->getConfInt("patFont",0); settings.mainFontPath=e->getConfString("mainFontPath",""); From adafb49be71207128dc8fa62455a02504a22cbd8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 15:12:39 -0500 Subject: [PATCH 077/105] GUI: prepare for interpolate --- src/gui/gui.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4da41cb35..59115f4a7 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3120,6 +3120,27 @@ void FurnaceGUI::doInterpolate() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_INTERPOLATE); + std::vector> points; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][iFine+1]!=-1) { + points.emplace(points.end(),j,pat->data[j][iFine+1]); + } + } + } + } + iFine=0; + } + makeUndo(GUI_UNDO_PATTERN_INTERPOLATE); } From a0c658f1d3bafd996f21bee072893790644ce0fe Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 21:06:47 -0500 Subject: [PATCH 078/105] GUI: implement interpolate values --- src/gui/gui.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 59115f4a7..d979b4d50 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3136,6 +3136,15 @@ void FurnaceGUI::doInterpolate() { points.emplace(points.end(),j,pat->data[j][iFine+1]); } } + + if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; + std::pair& nextPoint=points[j+1]; + double distance=nextPoint.first-curPoint.first; + for (int k=0; k<(nextPoint.first-curPoint.first); k++) { + pat->data[k+curPoint.first][iFine+1]=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); + } + } } } iFine=0; From bd705d837db220a1b23f765e3662f612cea0ac19 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 21:13:42 -0500 Subject: [PATCH 079/105] interpolate now works on notes --- src/gui/gui.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d979b4d50..2220299f8 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3145,6 +3145,30 @@ void FurnaceGUI::doInterpolate() { pat->data[k+curPoint.first][iFine+1]=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); } } + } else { + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][0]!=0 && pat->data[j][1]!=0) { + if (pat->data[j][0]!=100 && pat->data[j][0]!=101 && pat->data[j][0]!=102) { + points.emplace(points.end(),j,pat->data[j][0]+pat->data[j][1]*12); + } + } + } + + if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; + std::pair& nextPoint=points[j+1]; + double distance=nextPoint.first-curPoint.first; + for (int k=0; k<(nextPoint.first-curPoint.first); k++) { + int val=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); + pat->data[k+curPoint.first][0]=val%12; + pat->data[k+curPoint.first][1]=val/12; + if (pat->data[k+curPoint.first][0]==0) { + pat->data[k+curPoint.first][0]=12; + pat->data[k+curPoint.first][1]--; + } + pat->data[k+curPoint.first][1]&=255; + } + } } } iFine=0; From cd42a8b9f38f62816e5418aecdebc63c7faefae0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 02:36:52 -0500 Subject: [PATCH 080/105] GUI: implement flip --- src/gui/gui.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2220299f8..3b77476d6 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3330,6 +3330,31 @@ void FurnaceGUI::doFlip() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_FLIP); + DivPattern patBuffer; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + } + patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (iFine==0) { + pat->data[j][0]=patBuffer.data[selEnd.y-j+selStart.y][0]; + } + pat->data[j][iFine+1]=patBuffer.data[selEnd.y-j+selStart.y][iFine+1]; + } + } + iFine=0; + } + makeUndo(GUI_UNDO_PATTERN_FLIP); } From 6167feaf18b6e431560beeb341884c8d8c6c4de7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 03:13:11 -0500 Subject: [PATCH 081/105] GUI: implement shrink and expand! yay ONE MORE THING!!!!! then O P L --- src/gui/gui.cpp | 86 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 3b77476d6..4b46d3868 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3362,13 +3362,99 @@ void FurnaceGUI::doCollapse(int divider) { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_COLLAPSE); + DivPattern patBuffer; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + } + patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; + } + for (int j=0; j<=selEnd.y-selStart.y; j++) { + if (j*divider>=selEnd.y-selStart.y) { + if (iFine==0) { + pat->data[j+selStart.y][0]=0; + pat->data[j+selStart.y][1]=0; + } else { + pat->data[j+selStart.y][iFine+1]=-1; + } + } else { + if (iFine==0) { + pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y][0]; + } + pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y][iFine+1]; + + if (iFine==0) { + for (int k=1; k=selEnd.y-selStart.y) break; + if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break; + pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0]; + pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1]; + } + } else { + for (int k=1; k=selEnd.y-selStart.y) break; + if (pat->data[j+selStart.y][iFine+1]!=-1) break; + pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1]; + } + } + } + } + } + iFine=0; + } + makeUndo(GUI_UNDO_PATTERN_COLLAPSE); } void FurnaceGUI::doExpand(int multiplier) { + if (multiplier<1) return; + finishSelection(); prepareUndo(GUI_UNDO_PATTERN_EXPAND); + DivPattern patBuffer; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + } + patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; + } + for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { + if ((j+selStart.y)>=e->song.patLen) break; + if ((j%multiplier)!=0) { + if (iFine==0) { + pat->data[j+selStart.y][0]=0; + pat->data[j+selStart.y][1]=0; + } else { + pat->data[j+selStart.y][iFine+1]=-1; + } + continue; + } + if (iFine==0) { + pat->data[j+selStart.y][0]=patBuffer.data[j/multiplier+selStart.y][0]; + } + pat->data[j+selStart.y][iFine+1]=patBuffer.data[j/multiplier+selStart.y][iFine+1]; + } + } + iFine=0; + } + makeUndo(GUI_UNDO_PATTERN_EXPAND); } From a41736cc89b6559e8434a298a9c8e455934d3090 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 04:51:05 -0500 Subject: [PATCH 082/105] GUI: partially implement note input latch the UI for it is missing --- src/gui/gui.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4b46d3868..8c7981865 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4332,6 +4332,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { int num=12*curOctave+key; if (edit) { + // TODO: separate when adding MIDI input. DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); @@ -4353,7 +4354,17 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { pat->data[cursor.y][1]--; } pat->data[cursor.y][1]=(unsigned char)pat->data[cursor.y][1]; - pat->data[cursor.y][2]=curIns; + if (latchIns==-2) { + pat->data[cursor.y][2]=curIns; + } else if (latchIns!=-1 && !e->song.ins.empty()) { + pat->data[cursor.y][2]=MIN(((int)e->song.ins.size())-1,latchIns); + } + if (latchVol!=-1) { + int maxVol=e->getMaxVolumeChan(cursor.xCoarse); + pat->data[cursor.y][3]=MIN(maxVol,latchVol); + } + if (latchEffect!=-1) pat->data[cursor.y][4]=latchEffect; + if (latchEffectVal!=-1) pat->data[cursor.y][5]=latchEffectVal; previewNote(cursor.xCoarse,num); } makeUndo(GUI_UNDO_PATTERN_EDIT); @@ -6858,7 +6869,7 @@ FurnaceGUI::FurnaceGUI(): opMaskEffect(true), opMaskEffectVal(true), latchNote(-1), - latchIns(-1), + latchIns(-2), latchVol(-1), latchEffect(-1), latchEffectVal(-1), From 57631ac056252f114fc65aeec89affb6149fbfc7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 16:56:22 -0500 Subject: [PATCH 083/105] update release scripts --- scripts/release-win32.sh | 3 ++- scripts/release-win64.sh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/release-win32.sh b/scripts/release-win32.sh index d2c086d2e..215575a2b 100755 --- a/scripts/release-win32.sh +++ b/scripts/release-win32.sh @@ -1,6 +1,6 @@ #!/bin/bash # make Windows release -# this script shall be run from Linux with MinGW installed! +# this script shall be run from Arch Linux with MinGW installed! if [ ! -e /tmp/furnace ]; then ln -s "$PWD" /tmp/furnace || exit 1 @@ -14,6 +14,7 @@ fi cd win32build +# TODO: potential Arch-ism? i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" -DBUILD_SHARED_LIBS=OFF .. || exit 1 make -j8 || exit 1 i686-w64-mingw32-strip -s furnace.exe || exit 1 diff --git a/scripts/release-win64.sh b/scripts/release-win64.sh index 69bc602aa..f0dfc4d2c 100755 --- a/scripts/release-win64.sh +++ b/scripts/release-win64.sh @@ -1,6 +1,6 @@ #!/bin/bash # make Windows release -# this script shall be run from Linux with MinGW installed! +# this script shall be run from Arch Linux with MinGW installed! if [ ! -e /tmp/furnace ]; then ln -s "$PWD" /tmp/furnace || exit 1 @@ -14,6 +14,7 @@ fi cd winbuild +# TODO: potential Arch-ism? x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" .. || exit 1 make -j8 || exit 1 x86_64-w64-mingw32-strip -s furnace.exe || exit 1 From 6bca34725420dfa4a81c4db8800d7ccfc6207bb4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 17:29:43 -0500 Subject: [PATCH 084/105] maybe BUG --- extern/igfd/ImGuiFileDialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index 5be457930..3980bcd3e 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -3877,6 +3877,7 @@ namespace IGFD static ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_AllowDoubleClick | ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth; + // TODO BUG?! va_list args; va_start(args, vFmt); vsnprintf(fdi.puVariadicBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFmt, args); @@ -4074,6 +4075,7 @@ namespace IGFD if (ImGui::TableNextColumn()) // file name { + // TODO BUG?!?!?! needToBreakTheloop = prSelectableItem(i, infos, selected, _str.c_str()); if (needToBreakTheloop==2) escape=true; } From 3be56d50ab4aa288e39e0f09a90a7d1641b6a7cc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 17:30:43 -0500 Subject: [PATCH 085/105] GUI: prepare for two things - unified ins/wave/sample view - macro line drawing --- src/gui/gui.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/gui.h b/src/gui/gui.h index 0aec32566..dc985e8d0 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -534,6 +534,9 @@ class FurnaceGUI { int avoidRaisingPattern; int insFocusesPattern; int stepOnInsert; + // TODO flags + int unifiedDataView; + // end unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -663,6 +666,7 @@ class FurnaceGUI { bool macroDragInitialValueSet; bool macroDragInitialValue; bool macroDragChar; + bool macroDragLineMode; // TODO bool macroDragActive; ImVec2 macroLoopDragStart; From 5e77b474673a1ddcf691566167fd7e1b469fb71a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 18:32:35 -0500 Subject: [PATCH 086/105] system file picker anyone? DO NOT COMPILE! --- .gitmodules | 3 ++ CMakeLists.txt | 1 + extern/pfd | 1 + src/gui/fileDialog.cpp | 109 +++++++++++++++++++++++++++++++++++++++++ src/gui/fileDialog.h | 31 ++++++++++++ src/gui/gui.cpp | 27 +++++----- src/gui/gui.h | 7 +++ src/gui/settings.cpp | 14 ++++++ 8 files changed, 180 insertions(+), 13 deletions(-) create mode 160000 extern/pfd create mode 100644 src/gui/fileDialog.cpp create mode 100644 src/gui/fileDialog.h diff --git a/.gitmodules b/.gitmodules index d63fd70b5..d08aa505b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "extern/Nuked-OPL3"] path = extern/Nuked-OPL3 url = https://github.com/nukeykt/Nuked-OPL3.git +[submodule "extern/pfd"] + path = extern/pfd + url = https://github.com/samhocevar/portable-file-dialogs.git diff --git a/CMakeLists.txt b/CMakeLists.txt index de4eefbcc..99f1089b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -347,6 +347,7 @@ src/gui/font_unifont.cpp src/gui/font_icon.cpp src/gui/fonts.cpp src/gui/debug.cpp +src/gui/fileDialog.cpp src/gui/intConst.cpp src/gui/guiConst.cpp diff --git a/extern/pfd b/extern/pfd new file mode 160000 index 000000000..dea8520de --- /dev/null +++ b/extern/pfd @@ -0,0 +1 @@ +Subproject commit dea8520de18af09eefdbc18aaf7c24409d18491b diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp new file mode 100644 index 000000000..54b38739d --- /dev/null +++ b/src/gui/fileDialog.cpp @@ -0,0 +1,109 @@ +#include "fileDialog.h" +#include "ImGuiFileDialog.h" +#include "../../extern/pfd/portable-file-dialogs.h" + +bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, String path, double dpiScale) { + if (opened) return false; + saving=false; + curPath=path; + if (sysDialog) { + dialogO=new pfd::open_file(header,path,filter); + } else { + String parsedFilter; + if (filter.size()&1) return false; + + for (size_t i=0; iDpiScale=dpiScale; + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,parsedFilter.c_str(),path); + } + return true; +} + +bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, String path, double dpiScale) { + curPath=path; + if (sysDialog) { + // TODO + } else { + String parsedFilter; + ImGuiFileDialog::Instance()->DpiScale=dpiScale; + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,parsedFilter.c_str(),path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + } + return true; +} + +bool FurnaceGUIFileDialog::accepted() { + if (sysDialog) { + return (fileName!=""); + } else { + return ImGuiFileDialog::Instance()->IsOk(); + } +} + +void FurnaceGUIFileDialog::close() { + if (sysDialog) { + if (saving) { + if (dialogS!=NULL) { + delete dialogS; + dialogS=NULL; + } + } else { + if (dialogO!=NULL) { + delete dialogO; + dialogO=NULL; + printf("deleting\n"); + } + } + } else { + ImGuiFileDialog::Instance()->Close(); + } +} + +bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { + if (sysDialog) { + if (saving) { + if (dialogS!=NULL) { + if (dialogS->ready(1)) { + fileName=dialogS->result(); + printf("returning %s\n",fileName.c_str()); + return true; + } + } + } else { + if (dialogO!=NULL) { + if (dialogO->ready(1)) { + if (dialogO->result().empty()) { + fileName=""; + printf("returning nothing\n"); + } else { + fileName=dialogO->result()[0]; + printf("returning %s\n",fileName.c_str()); + } + return true; + } + } + } + return false; + } else { + return ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,min,max); + } +} + +String FurnaceGUIFileDialog::getPath() { + if (sysDialog) { + return curPath; + } else { + return ImGuiFileDialog::Instance()->GetCurrentPath(); + } +} + +String FurnaceGUIFileDialog::getFileName() { + if (sysDialog) { + return fileName; + } else { + return ImGuiFileDialog::Instance()->GetFilePathName(); + } +} \ No newline at end of file diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h new file mode 100644 index 000000000..057bef1fa --- /dev/null +++ b/src/gui/fileDialog.h @@ -0,0 +1,31 @@ +#include "../ta-utils.h" +#include "imgui.h" + +namespace pfd { + class open_file; + class save_file; +} + +class FurnaceGUIFileDialog { + bool sysDialog; + bool opened; + bool saving; + String curPath; + String fileName; + pfd::open_file* dialogO; + pfd::save_file* dialogS; + public: + bool openLoad(String header, std::vector filter, String path, double dpiScale); + bool openSave(String header, std::vector filter, String path, double dpiScale); + bool accepted(); + void close(); + bool render(const ImVec2& min, const ImVec2& max); + String getPath(); + String getFileName(); + FurnaceGUIFileDialog(bool system): + sysDialog(system), + opened(false), + saving(false), + dialogO(NULL), + dialogS(NULL) {} +}; \ No newline at end of file diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index eff4cd2e7..fe53b9113 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4585,11 +4585,11 @@ bool dirExists(String what) { } void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { - ImGuiFileDialog::Instance()->DpiScale=dpiScale; switch (type) { case GUI_FILE_OPEN: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","compatible files{.fur,.dmf},.*",workingDirSong); + fileDialog->openLoad("Open File",{"compatible files", ".fur,.dmf", "all files", ".*"},workingDirSong,dpiScale); + //ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","compatible files{.fur,.dmf},.*",workingDirSong); break; case GUI_FILE_SAVE: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); @@ -5942,42 +5942,42 @@ bool FurnaceGUI::loop() { if (patternOpen) nextWindow=GUI_WINDOW_PATTERN; } - if (ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,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))) { //ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard; switch (curFileDialog) { case GUI_FILE_OPEN: case GUI_FILE_SAVE: case GUI_FILE_SAVE_DMF_LEGACY: - workingDirSong=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirSong=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_INS_OPEN: case GUI_FILE_INS_SAVE: - workingDirIns=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_WAVE_OPEN: case GUI_FILE_WAVE_SAVE: - workingDirWave=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirWave=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_SAMPLE_OPEN: case GUI_FILE_SAMPLE_SAVE: - workingDirSample=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirSample=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_AUDIO_ONE: case GUI_FILE_EXPORT_AUDIO_PER_SYS: case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL: - workingDirAudioExport=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirAudioExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_VGM: case GUI_FILE_EXPORT_ROM: - workingDirVGMExport=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_LOAD_MAIN_FONT: case GUI_FILE_LOAD_PAT_FONT: - workingDirFont=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirFont=fileDialog->getPath()+DIR_SEPARATOR_STR; break; } - if (ImGuiFileDialog::Instance()->IsOk()) { - fileName=ImGuiFileDialog::Instance()->GetFilePathName(); + if (fileDialog->accepted()) { + fileName=fileDialog->getFileName(); if (fileName!="") { if (curFileDialog==GUI_FILE_SAVE) { if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song") { @@ -6103,7 +6103,7 @@ bool FurnaceGUI::loop() { curFileDialog=GUI_FILE_OPEN; } } - ImGuiFileDialog::Instance()->Close(); + fileDialog->close(); } if (warnQuit) { @@ -6805,6 +6805,7 @@ FurnaceGUI::FurnaceGUI(): displayNew(false), curFileDialog(GUI_FILE_OPEN), warnAction(GUI_WARN_OPEN), + fileDialog(NULL), scrW(1280), scrH(800), dpiScale(1), diff --git a/src/gui/gui.h b/src/gui/gui.h index dc985e8d0..9bf68e529 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -27,6 +27,8 @@ #include #include +#include "fileDialog.h" + #define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1); #define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;} @@ -476,6 +478,8 @@ class FurnaceGUI { FurnaceGUIFileDialogs curFileDialog; FurnaceGUIWarnings warnAction; + FurnaceGUIFileDialog* fileDialog; + int scrW, scrH; double dpiScale; @@ -536,6 +540,7 @@ class FurnaceGUI { int stepOnInsert; // TODO flags int unifiedDataView; + int sysFileDialog; // end unsigned int maxUndoSteps; String mainFontPath; @@ -581,6 +586,8 @@ class FurnaceGUI { avoidRaisingPattern(0), insFocusesPattern(1), stepOnInsert(0), + unifiedDataView(0), + sysFileDialog(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index c8f6763b4..eb09e86fa 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -182,6 +182,11 @@ void FurnaceGUI::drawSettings() { settings.restartOnFlagChange=restartOnFlagChangeB; } + bool sysFileDialogB=settings.sysFileDialog; + if (ImGui::Checkbox("Use system file picker",&sysFileDialogB)) { + settings.sysFileDialog=sysFileDialogB; + } + ImGui::Text("Wrap pattern cursor horizontally:"); if (ImGui::RadioButton("No##wrapH0",settings.wrapHorizontal==0)) { settings.wrapHorizontal=0; @@ -892,6 +897,8 @@ void FurnaceGUI::syncSettings() { settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0); settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); settings.stepOnInsert=e->getConfInt("stepOnInsert",0); + settings.unifiedDataView=e->getConfInt("unifiedDataView",0); + settings.sysFileDialog=e->getConfInt("sysFileDialog",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -930,6 +937,8 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.avoidRaisingPattern,0,1); clampSetting(settings.insFocusesPattern,0,1); clampSetting(settings.stepOnInsert,0,1); + clampSetting(settings.unifiedDataView,0,1); + clampSetting(settings.sysFileDialog,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1082,6 +1091,9 @@ void FurnaceGUI::syncSettings() { decodeKeyMap(noteKeys,e->getConfString("noteKeys",DEFAULT_NOTE_KEYS)); parseKeybinds(); + + if (fileDialog!=NULL) delete fileDialog; + fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); } #define PUT_UI_COLOR(source) e->setConf(#source,(int)ImGui::GetColorU32(uiColors[source])); @@ -1129,6 +1141,8 @@ void FurnaceGUI::commitSettings() { e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern); e->setConf("insFocusesPattern",settings.insFocusesPattern); e->setConf("stepOnInsert",settings.stepOnInsert); + e->setConf("unifiedDataView",settings.unifiedDataView); + e->setConf("sysFileDialog",settings.sysFileDialog); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From bfc44320230057b3d638941244721c6551be48d7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 21:06:08 -0500 Subject: [PATCH 087/105] nooooooooooooooooooooooooooooooooo --- src/gui/fileDialog.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h index 057bef1fa..05ee87b40 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -1,5 +1,6 @@ #include "../ta-utils.h" #include "imgui.h" +#include namespace pfd { class open_file; @@ -28,4 +29,4 @@ class FurnaceGUIFileDialog { saving(false), dialogO(NULL), dialogS(NULL) {} -}; \ No newline at end of file +}; From d9a93e0cec4faab1b0b68ac6f5b264de29cd57bb Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 21:19:52 -0500 Subject: [PATCH 088/105] ... --- src/gui/fileDialog.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index 54b38739d..60435b016 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -1,5 +1,10 @@ #include "fileDialog.h" #include "ImGuiFileDialog.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + #include "../../extern/pfd/portable-file-dialogs.h" bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, String path, double dpiScale) { @@ -106,4 +111,4 @@ String FurnaceGUIFileDialog::getFileName() { } else { return ImGuiFileDialog::Instance()->GetFilePathName(); } -} \ No newline at end of file +} From 2ba01857016441a9fa7f6cc29c1b5165be697756 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 21:46:52 -0500 Subject: [PATCH 089/105] well that didn't last long --- .gitmodules | 3 --- extern/pfd | 1 - 2 files changed, 4 deletions(-) delete mode 160000 extern/pfd diff --git a/.gitmodules b/.gitmodules index d08aa505b..d63fd70b5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,6 +22,3 @@ [submodule "extern/Nuked-OPL3"] path = extern/Nuked-OPL3 url = https://github.com/nukeykt/Nuked-OPL3.git -[submodule "extern/pfd"] - path = extern/pfd - url = https://github.com/samhocevar/portable-file-dialogs.git diff --git a/extern/pfd b/extern/pfd deleted file mode 160000 index dea8520de..000000000 --- a/extern/pfd +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dea8520de18af09eefdbc18aaf7c24409d18491b From 0874d58fb80423892ae1493478e8cbcecc1f73db Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 22:02:50 -0500 Subject: [PATCH 090/105] damn it --- CMakeLists.txt | 2 +- extern/pfd-fixed/.gitignore | 4 + extern/pfd-fixed/.lgtm.yml | 5 + extern/pfd-fixed/CMakeLists.txt | 6 + extern/pfd-fixed/COPYING | 14 + extern/pfd-fixed/README.md | 64 + extern/pfd-fixed/doc/message.md | 97 ++ extern/pfd-fixed/doc/notify.md | 40 + extern/pfd-fixed/doc/open_file.md | 90 ++ extern/pfd-fixed/doc/pfd.md | 120 ++ extern/pfd-fixed/doc/save_file.md | 73 + extern/pfd-fixed/doc/select_folder.md | 55 + extern/pfd-fixed/examples/.gitignore | 11 + extern/pfd-fixed/examples/example.cpp | 110 ++ extern/pfd-fixed/examples/example.vcxproj | 96 ++ extern/pfd-fixed/examples/kill.cpp | 42 + extern/pfd-fixed/examples/kill.vcxproj | 96 ++ extern/pfd-fixed/portable-file-dialogs.h | 1731 +++++++++++++++++++++ scripts/release-win32.sh | 2 +- scripts/release-win64.sh | 2 +- src/gui/fileDialog.cpp | 6 +- src/gui/gui.cpp | 3 + src/gui/settings.cpp | 3 - 23 files changed, 2661 insertions(+), 11 deletions(-) create mode 100644 extern/pfd-fixed/.gitignore create mode 100644 extern/pfd-fixed/.lgtm.yml create mode 100644 extern/pfd-fixed/CMakeLists.txt create mode 100644 extern/pfd-fixed/COPYING create mode 100644 extern/pfd-fixed/README.md create mode 100644 extern/pfd-fixed/doc/message.md create mode 100644 extern/pfd-fixed/doc/notify.md create mode 100644 extern/pfd-fixed/doc/open_file.md create mode 100644 extern/pfd-fixed/doc/pfd.md create mode 100644 extern/pfd-fixed/doc/save_file.md create mode 100644 extern/pfd-fixed/doc/select_folder.md create mode 100644 extern/pfd-fixed/examples/.gitignore create mode 100644 extern/pfd-fixed/examples/example.cpp create mode 100644 extern/pfd-fixed/examples/example.vcxproj create mode 100644 extern/pfd-fixed/examples/kill.cpp create mode 100644 extern/pfd-fixed/examples/kill.vcxproj create mode 100644 extern/pfd-fixed/portable-file-dialogs.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 99f1089b8..2dd372d2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -398,7 +398,7 @@ if (APPLE) endif() if (NOT MSVC) - set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter) + set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type) if (WARNINGS_ARE_ERRORS) list(APPEND WARNING_FLAGS -Werror) endif() diff --git a/extern/pfd-fixed/.gitignore b/extern/pfd-fixed/.gitignore new file mode 100644 index 000000000..ec121a999 --- /dev/null +++ b/extern/pfd-fixed/.gitignore @@ -0,0 +1,4 @@ +CMakeCache.txt +CMakeFiles +Makefile +cmake_install.cmake diff --git a/extern/pfd-fixed/.lgtm.yml b/extern/pfd-fixed/.lgtm.yml new file mode 100644 index 000000000..b4c4985b9 --- /dev/null +++ b/extern/pfd-fixed/.lgtm.yml @@ -0,0 +1,5 @@ +extraction: + cpp: + index: + build_command: + - make -C examples diff --git a/extern/pfd-fixed/CMakeLists.txt b/extern/pfd-fixed/CMakeLists.txt new file mode 100644 index 000000000..3be61ae5a --- /dev/null +++ b/extern/pfd-fixed/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.1.0) + +project(portable_file_dialogs VERSION 1.00 LANGUAGES CXX) + +add_library(${PROJECT_NAME} INTERFACE) +target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file diff --git a/extern/pfd-fixed/COPYING b/extern/pfd-fixed/COPYING new file mode 100644 index 000000000..8b014d64a --- /dev/null +++ b/extern/pfd-fixed/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/extern/pfd-fixed/README.md b/extern/pfd-fixed/README.md new file mode 100644 index 000000000..ceb36a405 --- /dev/null +++ b/extern/pfd-fixed/README.md @@ -0,0 +1,64 @@ +# Portable File Dialogs + +A free C++11 file dialog library. + +- works on Windows, Mac OS X, Linux +- **single-header**, no extra library dependencies +- **synchronous *or* asynchronous** (does not block the rest of your program!) +- **cancelable** (kill asynchronous dialogues without user interaction) +- **secure** (immune to shell-quote vulnerabilities) + +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/a25d3fd6959a4333871f630ac70b6e09)](https://www.codacy.com/manual/samhocevar/portable-file-dialogs?utm_source=github.com&utm_medium=referral&utm_content=samhocevar/portable-file-dialogs&utm_campaign=Badge_Grade) + +## Status + +The library is now pretty robust. It is not as feature-complete as +[Tiny File Dialogs](https://sourceforge.net/projects/tinyfiledialogs/), +but has asynchonous dialogs, more maintainable code, and fewer potential +security issues. + +The currently available backends are: + +- Win32 API (all known versions of Windows) +- Mac OS X (using AppleScript) +- GNOME desktop (using [Zenity](https://en.wikipedia.org/wiki/Zenity) or its clones Matedialog and Qarma) +- KDE desktop (using [KDialog](https://github.com/KDE/kdialog)) + +Experimental support for Emscripten is on its way. + +## Documentation + +- [`pfd`](doc/pfd.md) general documentation +- [`pfd::message`](doc/message.md) message box +- [`pfd::notify`](doc/notify.md) notification +- [`pfd::open_file`](doc/open_file.md) file open +- [`pfd::save_file`](doc/save_file.md) file save +- [`pfd::select_folder`](doc/select_folder.md) folder selection + +## History + +- 0.1.0 (July 16, 2020): first public release + +## Screenshots (Windows 10) + +![warning-win32](https://user-images.githubusercontent.com/245089/47136607-76919a00-d2b4-11e8-8f42-e2d62c4f9570.png) +![notify-win32](https://user-images.githubusercontent.com/245089/47142453-2ff76c00-d2c3-11e8-871a-1a110ac91eb2.png) +![open-win32](https://user-images.githubusercontent.com/245089/47155865-0f8cd900-d2e6-11e8-8041-1e20b6f77dee.png) + +## Screenshots (Mac OS X, dark theme) + +![warning-osxdark](https://user-images.githubusercontent.com/245089/56053001-22dba700-5d53-11e9-8233-ca7a2c58188d.png) +![notify-osxdark](https://user-images.githubusercontent.com/245089/56053188-bc0abd80-5d53-11e9-8298-68aa96315c6c.png) +![open-osxdark](https://user-images.githubusercontent.com/245089/56053378-39363280-5d54-11e9-9583-9f1c978fa0db.png) + +## Screenshots (Linux, GNOME desktop) + +![warning-gnome](https://user-images.githubusercontent.com/245089/47136608-772a3080-d2b4-11e8-9e1d-60a7e743e908.png) +![notify-gnome](https://user-images.githubusercontent.com/245089/47142455-30900280-d2c3-11e8-8b76-ea16c7e502d4.png) +![open-gnome](https://user-images.githubusercontent.com/245089/47155867-0f8cd900-d2e6-11e8-93af-275636491ec4.png) + +## Screenshots (Linux, KDE Plasma desktop) + +![warning-kde](https://user-images.githubusercontent.com/245089/47149255-4dcccd00-d2d3-11e8-84c9-f85612784680.png) +![notify-kde](https://user-images.githubusercontent.com/245089/47149206-27a72d00-d2d3-11e8-8f1b-96e462f08c2b.png) +![open-kde](https://user-images.githubusercontent.com/245089/47155866-0f8cd900-d2e6-11e8-8006-f14b948afc55.png) diff --git a/extern/pfd-fixed/doc/message.md b/extern/pfd-fixed/doc/message.md new file mode 100644 index 000000000..e54c44771 --- /dev/null +++ b/extern/pfd-fixed/doc/message.md @@ -0,0 +1,97 @@ +## Message Box API + +Displaying a message box is done using the `pfd::message` class. It can be provided a title, a +message text, a `choice` representing which buttons need to be rendered, and an `icon` for the +message: + +```cpp +pfd::message::message(std::string const &title, + std::string const &text, + pfd::choice choice = pfd::choice::ok_cancel, + pfd::icon icon = pfd::icon::info); + +enum class pfd::choice { ok, ok_cancel, yes_no, yes_no_cancel }; + +enum class pfd::icon { info, warning, error, question }; +``` + +The pressed button is queried using `pfd::message::result()`. If the dialog box is closed by any +other means, the `pfd::button::cancel` is assumed: + +```cpp +pfd::button pfd::message::result(); + +enum class pfd::button { ok, cancel, yes, no }; +``` + +It is possible to ask the dialog box whether the user took action using the `pfd::message::ready()` +method, with an optional `timeout` argument. If the user did not press a button within `timeout` +milliseconds, the function will return `false`: + +```cpp +bool pfd::message::ready(int timeout = pfd::default_wait_timeout); +``` + +## Example 1: simple notification + +The `pfd::message` destructor waits for user action, so this operation will block until the user +closes the message box: + +```cpp +pfd::message("Problem", "An error occurred while doing things", + pfd::choice::ok, pfd::icon::error); +``` + +## Example 2: retrieving the pressed button + +Using `pfd::message::result()` will also wait for user action before returning. This operation will block and return the user choice: + +```cpp +// Ask for user opinion +auto button = pfd::message("Action requested", "Do you want to proceed with things?", + pfd::choice::yes_no, pfd::icon::question).result(); +// Do something with button… +``` + +## Example 3: asynchronous message box + +Using `pfd::message::ready()` allows the application to perform other tasks while waiting for +user input: + +```cpp +// Message box with nice message +auto box = pfd::message("Unsaved Files", "Do you want to save the current " + "document before closing the application?", + pfd::choice::yes_no_cancel, + pfd::icon::warning); + +// Do something while waiting for user input +while (!box.ready(1000)) + std::cout << "Waited 1 second for user input...\n"; + +// Act depending on the selected button +switch (box.result()) +{ + case pfd::button::yes: std::cout << "User agreed.\n"; break; + case pfd::button::no: std::cout << "User disagreed.\n"; break; + case pfd::button::cancel: std::cout << "User freaked out.\n"; break; +} +``` + +## Screenshots + +#### Windows 10 + +![warning-win32](https://user-images.githubusercontent.com/245089/47136607-76919a00-d2b4-11e8-8f42-e2d62c4f9570.png) + +#### Mac OS X + +![warning-osx-dark](https://user-images.githubusercontent.com/245089/56053001-22dba700-5d53-11e9-8233-ca7a2c58188d.png) ![warning-osx-light](https://user-images.githubusercontent.com/245089/56053055-49014700-5d53-11e9-8306-e9a03a25e044.png) + +#### Linux (GNOME desktop) + +![warning-gnome](https://user-images.githubusercontent.com/245089/47140824-8662ab80-d2bf-11e8-9c87-2742dd5b58af.png) + +#### Linux (KDE desktop) + +![warning-kde](https://user-images.githubusercontent.com/245089/47149255-4dcccd00-d2d3-11e8-84c9-f85612784680.png) diff --git a/extern/pfd-fixed/doc/notify.md b/extern/pfd-fixed/doc/notify.md new file mode 100644 index 000000000..b140e2619 --- /dev/null +++ b/extern/pfd-fixed/doc/notify.md @@ -0,0 +1,40 @@ +## Notification API + +Displaying a desktop notification is done using the `pfd::notify` class. It can be provided a +title, a message text, and an `icon` for the notification style: + +```cpp +pfd::notify::notify(std::string const &title, + std::string const &text, + pfd::icon icon = pfd::icon::info); + +enum class pfd::icon { info, warning, error }; +``` + +## Example + +Displaying a notification is straightforward. Emoji are supported: + +```cpp +pfd::notify("System event", "Something might be on fire 🔥", + pfd::icon::warning); +``` + +The `pfd::notify` object needs not be kept around, letting the object clean up itself is enough. + +## Screenshots + +Windows 10: +![notify-win32](https://user-images.githubusercontent.com/245089/47142453-2ff76c00-d2c3-11e8-871a-1a110ac91eb2.png) + +Mac OS X (dark theme): +![image](https://user-images.githubusercontent.com/245089/56053188-bc0abd80-5d53-11e9-8298-68aa96315c6c.png) + +Mac OS X (light theme): +![image](https://user-images.githubusercontent.com/245089/56053137-92ea2d00-5d53-11e9-8cf2-049486c45713.png) + +Linux (GNOME desktop): +![notify-gnome](https://user-images.githubusercontent.com/245089/47142455-30900280-d2c3-11e8-8b76-ea16c7e502d4.png) + +Linux (KDE desktop): +![notify-kde](https://user-images.githubusercontent.com/245089/47149206-27a72d00-d2d3-11e8-8f1b-96e462f08c2b.png) diff --git a/extern/pfd-fixed/doc/open_file.md b/extern/pfd-fixed/doc/open_file.md new file mode 100644 index 000000000..db3158ef2 --- /dev/null +++ b/extern/pfd-fixed/doc/open_file.md @@ -0,0 +1,90 @@ +## File Open API + +The `pfd::open_file` class handles file opening dialogs. It can be provided a title, a starting +directory and/or pre-selected file, an optional filter for recognised file types, and an optional +flag to allow multiple selection: + +```cpp +pfd::open_file::open_file(std::string const &title, + std::string const &initial_path, + std::vector filters = { "All Files", "*" }, + pfd::opt option = pfd::opt::none); +``` + +The `option` parameter can be `pfd::opt::multiselect` to allow selecting multiple files. + +The selected files are queried using `pfd::open_file::result()`. If the user canceled the +operation, the returned list is empty: + +```cpp +std::vector pfd::open_file::result(); +``` + +It is possible to ask the file open dialog whether the user took action using the +`pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate +the dialog within `timeout` milliseconds, the function will return `false`: + +```cpp +bool pfd::open_file::ready(int timeout = pfd::default_wait_timeout); +``` + +## Example 1: simple file selection + +Using `pfd::open_file::result()` will wait for user action before returning. This operation will +block and return the user choice: + +```cpp +auto selection = pfd::open_file("Select a file").result(); +if (!selection.empty()) + std::cout << "User selected file " << selection[0] << "\n"; +``` + +## Example 2: filters + +The filter list enumerates filter names and corresponded space-separated wildcard lists. It +defaults to `{ "All Files", "*" }`, but here is how to use other options: + +```cpp +auto selection = pfd::open_file("Select a file", ".", + { "Image Files", "*.png *.jpg *.jpeg *.bmp", + "Audio Files", "*.wav *.mp3", + "All Files", "*" }, + pfd::opt::multiselect).result(); +// Do something with selection +for (auto const &filename : dialog.result()) + std::cout << "Selected file: " << filename << "\n"; +``` + +## Example 3: asynchronous file open + +Using `pfd::open_file::ready()` allows the application to perform other tasks while waiting for +user input: + +```cpp +// File open dialog +auto dialog = pfd::open_file("Select file to open"); + +// Do something while waiting for user input +while (!dialog.ready(1000)) + std::cout << "Waited 1 second for user input...\n"; + +// Act depending on the user choice +std::cout << "Number of selected files: " << dialog.result().size() << "\n"; +``` + +## Screenshots + +Windows 10: +![open-win32](https://user-images.githubusercontent.com/245089/47155865-0f8cd900-d2e6-11e8-8041-1e20b6f77dee.png) + +Mac OS X (dark theme): +![image](https://user-images.githubusercontent.com/245089/56053378-39363280-5d54-11e9-9583-9f1c978fa0db.png) + +Mac OS X (light theme): +![image](https://user-images.githubusercontent.com/245089/56053413-4fdc8980-5d54-11e9-85e3-e9e5d0e10772.png) + +Linux (GNOME desktop): +![open-gnome](https://user-images.githubusercontent.com/245089/47155867-0f8cd900-d2e6-11e8-93af-275636491ec4.png) + +Linux (KDE desktop): +![open-kde](https://user-images.githubusercontent.com/245089/47155866-0f8cd900-d2e6-11e8-8006-f14b948afc55.png) diff --git a/extern/pfd-fixed/doc/pfd.md b/extern/pfd-fixed/doc/pfd.md new file mode 100644 index 000000000..f62799efc --- /dev/null +++ b/extern/pfd-fixed/doc/pfd.md @@ -0,0 +1,120 @@ +## Portable File Dialogs documentation + +The library can be used either as a [header-only library](https://en.wikipedia.org/wiki/Header-only), +or as a [single file library](https://github.com/nothings/single_file_libs). + +### Use as header-only library + +Just include the main header file wherever needed: + +```cpp +#include "portable-file-dialogs.h" + +/* ... */ + + pfd::message::message("Hello", "This is a test"); + +/* ... */ +``` + +### Use as a single-file library + +Defining the `PFD_SKIP_IMPLEMENTATION` macro before including `portable-file-dialogs.h` will +skip all the implementation code and reduce compilation times. You still need to include the +header without the macro at least once, typically in a `pfd-impl.cpp` file. + +```cpp +// In pfd-impl.cpp +#include "portable-file-dialogs.h" +``` + +```cpp +// In all other files +#define PFD_SKIP_IMPLEMENTATION 1 +#include "portable-file-dialogs.h" +``` + +### General concepts + +Dialogs inherit from `pfd::dialog` and are created by calling their class constructor. Their +destructor will block until the window is closed by user interaction. So for instance this +will block until the end of the line: + +```cpp +pfd::message::message("Hi", "there"); +``` + +Whereas this will only block until the end of the scope, allowing the program to perform +additional operations while the dialog is open: + +```cpp +{ + auto m = pfd::message::message("Hi", "there"); + + // ... perform asynchronous operations here +} +``` + +It is possible to call `bool pfd::dialog::ready(timeout)` on the dialog in order to query its +status and perform asynchronous operations as long as the user has not interacted: + +```cpp +{ + auto m = pfd::message::message("Hi", "there"); + + while (!m.ready()) + { + // ... perform asynchronous operations here + } +} +``` + +If necessary, a dialog can be forcibly closed using `bool pfd::dialog::kill()`. Note that this +may be confusing to the user and should only be used in very specific situations. It is also not +possible to close a Windows message box that provides no _Cancel_ button. + +```cpp +{ + auto m = pfd::message::message("Hi", "there"); + + while (!m.ready()) + { + // ... perform asynchronous operations here + + if (too_much_time_has_passed()) + m.kill(); + } +} +``` + +Finally, the user response can be retrieved using `pfd::dialog::result()`. The return value of +this function depends on which dialog is being used. See their respective documentation for more +information: + + * [`pfd::message`](message.md) (message box) + * [`pfd::notify`](notify.md) (notification) + * [`pfd::open_file`](open_file.md) (file open) + * [`pfd::save_file`](save_file.md) (file save) + * [`pfd::select_folder`](select_folder.md) (folder selection) + +### Settings + +The library can be queried and configured through the `pfd::settings` class. + +```cpp +bool pfd::settings::available(); +void pfd::settings::verbose(bool value); +void pfd::settings::rescan(); +``` + +The return value of `pfd::settings::available()` indicates whether a suitable dialog backend (such +as Zenity or KDialog on Linux) has been found. If not, the library will not work and all dialog +invocations will be no-ops. The program will not crash but you should account for this situation +and add a fallback mechanism or exit gracefully. + +Calling `pfd::settings::rescan()` will force a rescan of available backends. This may change the +result of `pfd::settings::available()` if a backend was installed on the system in the meantime. +This is probably only useful for debugging purposes. + +Calling `pfd::settings::verbose(true)` may help debug the library. It will output debug information +to `std::cout` about some operations being performed. diff --git a/extern/pfd-fixed/doc/save_file.md b/extern/pfd-fixed/doc/save_file.md new file mode 100644 index 000000000..5c10badb6 --- /dev/null +++ b/extern/pfd-fixed/doc/save_file.md @@ -0,0 +1,73 @@ +## File Open API + +The `pfd::save_file` class handles file saving dialogs. It can be provided a title, a starting +directory and/or pre-selected file, an optional filter for recognised file types, and an optional +flag to allow multiple selection: + +```cpp +pfd::save_file::save_file(std::string const &title, + std::string const &initial_path, + std::vector filters = { "All Files", "*" }, + pfd::opt option = pfd::opt::none); +``` + +The `option` parameter can be `pfd::opt::force_overwrite` to disable a potential warning when +saving to an existing file. + +The selected file is queried using `pfd::save_file::result()`. If the user canceled the +operation, the returned file name is empty: + +```cpp +std::string pfd::save_file::result(); +``` + +It is possible to ask the file save dialog whether the user took action using the +`pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate +the dialog within `timeout` milliseconds, the function will return `false`: + +```cpp +bool pfd::save_file::ready(int timeout = pfd::default_wait_timeout); +``` + +## Example 1: simple file selection + +Using `pfd::save_file::result()` will wait for user action before returning. This operation will +block and return the user choice: + +```cpp +auto destination = pfd::save_file("Select a file").result(); +if (!destination.empty()) + std::cout << "User selected file " << destination << "\n"; +``` + +## Example 2: filters + +The filter list enumerates filter names and corresponded space-separated wildcard lists. It +defaults to `{ "All Files", "*" }`, but here is how to use other options: + +```cpp +auto destination = pfd::save_file("Select a file", ".", + { "Image Files", "*.png *.jpg *.jpeg *.bmp", + "Audio Files", "*.wav *.mp3", + "All Files", "*" }, + pfd::opt::force_overwrite).result(); +// Do something with destination +std::cout << "Selected file: " << destination << "\n"; +``` + +## Example 3: asynchronous file save + +Using `pfd::save_file::ready()` allows the application to perform other tasks while waiting for +user input: + +```cpp +// File save dialog +auto dialog = pfd::save_file("Select file to save"); + +// Do something while waiting for user input +while (!dialog.ready(1000)) + std::cout << "Waited 1 second for user input...\n"; + +// Act depending on the user choice +std::cout << "User selected file: " << dialog.result() << "\n"; +``` diff --git a/extern/pfd-fixed/doc/select_folder.md b/extern/pfd-fixed/doc/select_folder.md new file mode 100644 index 000000000..28f5f63fa --- /dev/null +++ b/extern/pfd-fixed/doc/select_folder.md @@ -0,0 +1,55 @@ +## Folder Selection API + +The `pfd::select_folder` class handles folder opening dialogs. It can be provided a title, and an +optional starting directory: + +```cpp +pfd::select_folder::select_folder(std::string const &title, + std::string const &default_path = "", + pfd::opt option = pfd::opt::none); +``` + +The `option` parameter can be `pfd::opt::force_path` to force the operating system to use the +provided path. Some systems default to the most recently used path, if applicable. + +The selected folder is queried using `pfd::select_folder::result()`. If the user canceled the +operation, the returned string is empty: + +```cpp +std::string pfd::select_folder::result(); +``` + +It is possible to ask the folder selection dialog whether the user took action using the +`pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate +the dialog within `timeout` milliseconds, the function will return `false`: + +```cpp +bool pfd::select_folder::ready(int timeout = pfd::default_wait_timeout); +``` + +## Example 1: simple folder selection + +Using `pfd::select_folder::result()` will wait for user action before returning. This operation +will block and return the user choice: + +```cpp +auto selection = pfd::select_folder("Select a folder").result(); +if (!selection.empty()) + std::cout << "User selected folder " << selection << "\n"; +``` + +## Example 2: asynchronous folder open + +Using `pfd::select_folder::ready()` allows the application to perform other tasks while waiting for user input: + +```cpp +// Folder selection dialog +auto dialog = pfd::select_folder("Select folder to open"); + +// Do something while waiting for user input +while (!dialog.ready(1000)) + std::cout << "Waited 1 second for user input...\n"; + +// Act depending on the user choice +std::cout << "Selected folder: " << dialog.result() << "\n"; +``` diff --git a/extern/pfd-fixed/examples/.gitignore b/extern/pfd-fixed/examples/.gitignore new file mode 100644 index 000000000..bda6acf70 --- /dev/null +++ b/extern/pfd-fixed/examples/.gitignore @@ -0,0 +1,11 @@ +example +example.exe +kill +kill.exe + +Debug +Release +*.vcxproj.user + +.idea +cmake-build-* diff --git a/extern/pfd-fixed/examples/example.cpp b/extern/pfd-fixed/examples/example.cpp new file mode 100644 index 000000000..a216ae4b3 --- /dev/null +++ b/extern/pfd-fixed/examples/example.cpp @@ -0,0 +1,110 @@ +// +// Portable File Dialogs +// +// Copyright © 2018—2020 Sam Hocevar +// +// This program is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#include "portable-file-dialogs.h" + +#include + +#if _WIN32 +#define DEFAULT_PATH "C:\\" +#else +#define DEFAULT_PATH "/tmp" +#endif + +int main() +{ + // Check that a backend is available + if (!pfd::settings::available()) + { + std::cout << "Portable File Dialogs are not available on this platform.\n"; + return 1; + } + + // Set verbosity to true + pfd::settings::verbose(true); + + // Notification + pfd::notify("Important Notification", + "This is ' a message, pay \" attention \\ to it!", + pfd::icon::info); + + // Message box with nice message + auto m = pfd::message("Personal Message", + "You are an amazing person, don’t let anyone make you think otherwise.", + pfd::choice::yes_no_cancel, + pfd::icon::warning); + + // Optional: do something while waiting for user action + for (int i = 0; i < 10 && !m.ready(1000); ++i) + std::cout << "Waited 1 second for user input...\n"; + + // Do something according to the selected button + switch (m.result()) + { + case pfd::button::yes: std::cout << "User agreed.\n"; break; + case pfd::button::no: std::cout << "User disagreed.\n"; break; + case pfd::button::cancel: std::cout << "User freaked out.\n"; break; + default: break; // Should not happen + } + + // Directory selection + auto dir = pfd::select_folder("Select any directory", DEFAULT_PATH).result(); + std::cout << "Selected dir: " << dir << "\n"; + + // File open + auto f = pfd::open_file("Choose files to read", DEFAULT_PATH, + { "Text Files (.txt .text)", "*.txt *.text", + "All Files", "*" }, + pfd::opt::multiselect); + std::cout << "Selected files:"; + for (auto const &name : f.result()) + std::cout << " " + name; + std::cout << "\n"; +} + +// Unused function that just tests the whole API +void api() +{ + // pfd::settings + pfd::settings::verbose(true); + pfd::settings::rescan(); + + // pfd::notify + pfd::notify("", ""); + pfd::notify("", "", pfd::icon::info); + pfd::notify("", "", pfd::icon::warning); + pfd::notify("", "", pfd::icon::error); + pfd::notify("", "", pfd::icon::question); + + pfd::notify a("", ""); + (void)a.ready(); + (void)a.ready(42); + + // pfd::message + pfd::message("", ""); + pfd::message("", "", pfd::choice::ok); + pfd::message("", "", pfd::choice::ok_cancel); + pfd::message("", "", pfd::choice::yes_no); + pfd::message("", "", pfd::choice::yes_no_cancel); + pfd::message("", "", pfd::choice::retry_cancel); + pfd::message("", "", pfd::choice::abort_retry_ignore); + pfd::message("", "", pfd::choice::ok, pfd::icon::info); + pfd::message("", "", pfd::choice::ok, pfd::icon::warning); + pfd::message("", "", pfd::choice::ok, pfd::icon::error); + pfd::message("", "", pfd::choice::ok, pfd::icon::question); + + pfd::message b("", ""); + (void)b.ready(); + (void)b.ready(42); + (void)b.result(); +} + diff --git a/extern/pfd-fixed/examples/example.vcxproj b/extern/pfd-fixed/examples/example.vcxproj new file mode 100644 index 000000000..d7e914928 --- /dev/null +++ b/extern/pfd-fixed/examples/example.vcxproj @@ -0,0 +1,96 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + 15.0 + {10F4364D-27C4-4C74-8079-7C42971E81E7} + Win32Proj + example + 10.0 + + + + Application + v142 + Unicode + + + true + + + false + true + + + + + + + + + + + + true + + + false + + + + .. + NotUsing + Level3 + true + true + + + Console + true + + + + + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + + + MaxSpeed + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + true + + + true + true + + + + + + \ No newline at end of file diff --git a/extern/pfd-fixed/examples/kill.cpp b/extern/pfd-fixed/examples/kill.cpp new file mode 100644 index 000000000..787edbeb3 --- /dev/null +++ b/extern/pfd-fixed/examples/kill.cpp @@ -0,0 +1,42 @@ +// +// Portable File Dialogs +// +// Copyright © 2018—2020 Sam Hocevar +// +// This program is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#include "portable-file-dialogs.h" + +#include + +int main() +{ + // Set verbosity to true + pfd::settings::verbose(true); + + // Message box with nice message + auto m = pfd::message("Upgrade software?", + "Press OK to upgrade this software.\n" + "\n" + "By default, the software will update itself\n" + "automatically in 10 seconds.", + pfd::choice::ok_cancel, + pfd::icon::warning); + + // Wait for an answer for up to 10 seconds + for (int i = 0; i < 10 && !m.ready(1000); ++i) + ; + + // Upgrade software if user clicked OK, or if user didn’t interact + bool upgrade = m.ready() ? m.result() == pfd::button::ok : m.kill(); + if (upgrade) + std::cout << "Upgrading software!\n"; + else + std::cout << "Not upgrading software.\n"; +} + diff --git a/extern/pfd-fixed/examples/kill.vcxproj b/extern/pfd-fixed/examples/kill.vcxproj new file mode 100644 index 000000000..b1ee3c9c4 --- /dev/null +++ b/extern/pfd-fixed/examples/kill.vcxproj @@ -0,0 +1,96 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + 15.0 + {B94D26B1-7EF7-43A2-A973-9A96A08E2E17} + Win32Proj + kill + 10.0 + + + + Application + v142 + Unicode + + + true + + + false + true + + + + + + + + + + + + true + + + false + + + + .. + NotUsing + Level3 + true + true + + + Console + true + + + + + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + + + MaxSpeed + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + true + + + true + true + + + + + + diff --git a/extern/pfd-fixed/portable-file-dialogs.h b/extern/pfd-fixed/portable-file-dialogs.h new file mode 100644 index 000000000..41e588acf --- /dev/null +++ b/extern/pfd-fixed/portable-file-dialogs.h @@ -0,0 +1,1731 @@ +// +// Portable File Dialogs +// +// Copyright © 2018—2020 Sam Hocevar +// +// This library is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +#if _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#include +#include +#include // IFileDialog +#include +#include +#include // std::async + +#elif __EMSCRIPTEN__ +#include + +#else +#ifndef _POSIX_C_SOURCE +# define _POSIX_C_SOURCE 2 // for popen() +#endif +#ifdef __APPLE__ +# ifndef _DARWIN_C_SOURCE +# define _DARWIN_C_SOURCE +# endif +#endif +#include // popen() +#include // std::getenv() +#include // fcntl() +#include // read(), pipe(), dup2() +#include // ::kill, std::signal +#include // waitpid() +#endif + +#include // std::string +#include // std::shared_ptr +#include // std::ostream +#include // std::map +#include // std::set +#include // std::regex +#include // std::mutex, std::this_thread +#include // std::chrono + +// Versions of mingw64 g++ up to 9.3.0 do not have a complete IFileDialog +#ifndef PFD_HAS_IFILEDIALOG +# define PFD_HAS_IFILEDIALOG 1 +# if (defined __MINGW64__ || defined __MINGW32__) && defined __GXX_ABI_VERSION +# if __GXX_ABI_VERSION <= 1013 +# undef PFD_HAS_IFILEDIALOG +# define PFD_HAS_IFILEDIALOG 0 +# endif +# endif +#endif + +namespace pfd +{ + +enum class button +{ + cancel = -1, + ok, + yes, + no, + abort, + retry, + ignore, +}; + +enum class choice +{ + ok = 0, + ok_cancel, + yes_no, + yes_no_cancel, + retry_cancel, + abort_retry_ignore, +}; + +enum class icon +{ + info = 0, + warning, + error, + question, +}; + +// Additional option flags for various dialog constructors +enum class opt : uint8_t +{ + none = 0, + // For file open, allow multiselect. + multiselect = 0x1, + // For file save, force overwrite and disable the confirmation dialog. + force_overwrite = 0x2, + // For folder select, force path to be the provided argument instead + // of the last opened directory, which is the Microsoft-recommended, + // user-friendly behaviour. + force_path = 0x4, +}; + +inline opt operator |(opt a, opt b) { return opt(uint8_t(a) | uint8_t(b)); } +inline bool operator &(opt a, opt b) { return bool(uint8_t(a) & uint8_t(b)); } + +// The settings class, only exposing to the user a way to set verbose mode +// and to force a rescan of installed desktop helpers (zenity, kdialog…). +class settings +{ +public: + static bool available(); + + static void verbose(bool value); + static void rescan(); + +protected: + explicit settings(bool resync = false); + + bool check_program(std::string const &program); + + inline bool is_osascript() const; + inline bool is_zenity() const; + inline bool is_kdialog() const; + + enum class flag + { + is_scanned = 0, + is_verbose, + + has_zenity, + has_matedialog, + has_qarma, + has_kdialog, + is_vista, + + max_flag, + }; + + // Static array of flags for internal state + bool const &flags(flag in_flag) const; + + // Non-const getter for the static array of flags + bool &flags(flag in_flag); +}; + +// Internal classes, not to be used by client applications +namespace internal +{ + +// Process wait timeout, in milliseconds +static int const default_wait_timeout = 20; + +class executor +{ + friend class dialog; + +public: + // High level function to get the result of a command + std::string result(int *exit_code = nullptr); + + // High level function to abort + bool kill(); + +#if _WIN32 + void start_func(std::function const &fun); + static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam); +#elif __EMSCRIPTEN__ + void start(int exit_code); +#else + void start_process(std::vector const &command); +#endif + + ~executor(); + +protected: + bool ready(int timeout = default_wait_timeout); + void stop(); + +private: + bool m_running = false; + std::string m_stdout; + int m_exit_code = -1; +#if _WIN32 + std::future m_future; + std::set m_windows; + std::condition_variable m_cond; + std::mutex m_mutex; + DWORD m_tid; +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something +#else + pid_t m_pid = 0; + int m_fd = -1; +#endif +}; + +class platform +{ +protected: +#if _WIN32 + // Helper class around LoadLibraryA() and GetProcAddress() with some safety + class dll + { + public: + dll(std::string const &name); + ~dll(); + + template class proc + { + public: + proc(dll const &lib, std::string const &sym) + : m_proc(reinterpret_cast(::GetProcAddress(lib.handle, sym.c_str()))) + {} + + operator bool() const { return m_proc != nullptr; } + operator T *() const { return m_proc; } + + private: + T *m_proc; + }; + + private: + HMODULE handle; + }; + + // Helper class around CoInitialize() and CoUnInitialize() + class ole32_dll : public dll + { + public: + ole32_dll(); + ~ole32_dll(); + bool is_initialized(); + + private: + HRESULT m_state; + }; + + // Helper class around CreateActCtx() and ActivateActCtx() + class new_style_context + { + public: + new_style_context(); + ~new_style_context(); + + private: + HANDLE create(); + ULONG_PTR m_cookie = 0; + }; +#endif +}; + +class dialog : protected settings, protected platform +{ +public: + bool ready(int timeout = default_wait_timeout) const; + bool kill() const; + +protected: + explicit dialog(); + + std::vector desktop_helper() const; + static std::string buttons_to_name(choice _choice); + static std::string get_icon_name(icon _icon); + + std::string powershell_quote(std::string const &str) const; + std::string osascript_quote(std::string const &str) const; + std::string shell_quote(std::string const &str) const; + + // Keep handle to executing command + std::shared_ptr m_async; +}; + +class file_dialog : public dialog +{ +protected: + enum type + { + open, + save, + folder, + }; + + file_dialog(type in_type, + std::string const &title, + std::string const &default_path = "", + std::vector const &filters = {}, + opt options = opt::none); + +protected: + std::string string_result(); + std::vector vector_result(); + +#if _WIN32 + static int CALLBACK bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData); +#if PFD_HAS_IFILEDIALOG + std::string select_folder_vista(IFileDialog *ifd, bool force_path); +#endif + + std::wstring m_wtitle; + std::wstring m_wdefault_path; + + std::vector m_vector_result; +#endif +}; + +} // namespace internal + +// +// The notify widget +// + +class notify : public internal::dialog +{ +public: + notify(std::string const &title, + std::string const &message, + icon _icon = icon::info); +}; + +// +// The message widget +// + +class message : public internal::dialog +{ +public: + message(std::string const &title, + std::string const &text, + choice _choice = choice::ok_cancel, + icon _icon = icon::info); + + button result(); + +private: + // Some extra logic to map the exit code to button number + std::map m_mappings; +}; + +// +// The open_file, save_file, and open_folder widgets +// + +class open_file : public internal::file_dialog +{ +public: + open_file(std::string const &title, + std::string const &default_path = "", + std::vector const &filters = { "All Files", "*" }, + opt options = opt::none); + +#if defined(__has_cpp_attribute) +#if __has_cpp_attribute(deprecated) + // Backwards compatibility + [[deprecated("Use pfd::opt::multiselect instead of allow_multiselect")]] +#endif +#endif + open_file(std::string const &title, + std::string const &default_path, + std::vector const &filters, + bool allow_multiselect); + + std::vector result(); +}; + +class save_file : public internal::file_dialog +{ +public: + save_file(std::string const &title, + std::string const &default_path = "", + std::vector const &filters = { "All Files", "*" }, + opt options = opt::none); + +#if defined(__has_cpp_attribute) +#if __has_cpp_attribute(deprecated) + // Backwards compatibility + [[deprecated("Use pfd::opt::force_overwrite instead of confirm_overwrite")]] +#endif +#endif + save_file(std::string const &title, + std::string const &default_path, + std::vector const &filters, + bool confirm_overwrite); + + std::string result(); +}; + +class select_folder : public internal::file_dialog +{ +public: + select_folder(std::string const &title, + std::string const &default_path = "", + opt options = opt::none); + + std::string result(); +}; + +// +// Below this are all the method implementations. You may choose to define the +// macro PFD_SKIP_IMPLEMENTATION everywhere before including this header except +// in one place. This may reduce compilation times. +// + +#if !defined PFD_SKIP_IMPLEMENTATION + +// internal free functions implementations + +namespace internal +{ + +#if _WIN32 +static inline std::wstring str2wstr(std::string const &str) +{ + int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0); + std::wstring ret(len, '\0'); + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPWSTR)ret.data(), (int)ret.size()); + return ret; +} + +static inline std::string wstr2str(std::wstring const &str) +{ + int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0, nullptr, nullptr); + std::string ret(len, '\0'); + WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPSTR)ret.data(), (int)ret.size(), nullptr, nullptr); + return ret; +} + +static inline bool is_vista() +{ + OSVERSIONINFOEXW osvi; + memset(&osvi, 0, sizeof(osvi)); + DWORDLONG const mask = VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + osvi.dwOSVersionInfoSize = sizeof(osvi); + osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA); + osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA); + osvi.wServicePackMajor = 0; + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask) != FALSE; +} +#endif + +// This is necessary until C++20 which will have std::string::ends_with() etc. + +static inline bool ends_with(std::string const &str, std::string const &suffix) +{ + return suffix.size() <= str.size() && + str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +static inline bool starts_with(std::string const &str, std::string const &prefix) +{ + return prefix.size() <= str.size() && + str.compare(0, prefix.size(), prefix) == 0; +} + +} // namespace internal + +// settings implementation + +inline settings::settings(bool resync) +{ + flags(flag::is_scanned) &= !resync; + + if (flags(flag::is_scanned)) + return; + +#if _WIN32 + flags(flag::is_vista) = internal::is_vista(); +#elif !__APPLE__ + flags(flag::has_zenity) = check_program("zenity"); + flags(flag::has_matedialog) = check_program("matedialog"); + flags(flag::has_qarma) = check_program("qarma"); + flags(flag::has_kdialog) = check_program("kdialog"); + + // If multiple helpers are available, try to default to the best one + if (flags(flag::has_zenity) && flags(flag::has_kdialog)) + { + auto desktop_name = std::getenv("XDG_SESSION_DESKTOP"); + if (desktop_name && desktop_name == std::string("gnome")) + flags(flag::has_kdialog) = false; + else if (desktop_name && desktop_name == std::string("KDE")) + flags(flag::has_zenity) = false; + } +#endif + + flags(flag::is_scanned) = true; +} + +inline bool settings::available() +{ +#if _WIN32 + return true; +#elif __APPLE__ + return true; +#else + settings tmp; + return tmp.flags(flag::has_zenity) || + tmp.flags(flag::has_matedialog) || + tmp.flags(flag::has_qarma) || + tmp.flags(flag::has_kdialog); +#endif +} + +inline void settings::verbose(bool value) +{ + settings().flags(flag::is_verbose) = value; +} + +inline void settings::rescan() +{ + settings(/* resync = */ true); +} + +// Check whether a program is present using “which”. +inline bool settings::check_program(std::string const &program) +{ +#if _WIN32 + (void)program; + return false; +#elif __EMSCRIPTEN__ + (void)program; + return false; +#else + int exit_code = -1; + internal::executor async; + async.start_process({"/bin/sh", "-c", "which " + program}); + async.result(&exit_code); + return exit_code == 0; +#endif +} + +inline bool settings::is_osascript() const +{ +#if __APPLE__ + return true; +#else + return false; +#endif +} + +inline bool settings::is_zenity() const +{ + return flags(flag::has_zenity) || + flags(flag::has_matedialog) || + flags(flag::has_qarma); +} + +inline bool settings::is_kdialog() const +{ + return flags(flag::has_kdialog); +} + +inline bool const &settings::flags(flag in_flag) const +{ + static bool flags[size_t(flag::max_flag)]; + return flags[size_t(in_flag)]; +} + +inline bool &settings::flags(flag in_flag) +{ + return const_cast(static_cast(this)->flags(in_flag)); +} + +// executor implementation + +inline std::string internal::executor::result(int *exit_code /* = nullptr */) +{ + stop(); + if (exit_code) + *exit_code = m_exit_code; + return m_stdout; +} + +inline bool internal::executor::kill() +{ +#if _WIN32 + if (m_future.valid()) + { + // Close all windows that weren’t open when we started the future + auto previous_windows = m_windows; + EnumWindows(&enum_windows_callback, (LPARAM)this); + for (auto hwnd : m_windows) + if (previous_windows.find(hwnd) == previous_windows.end()) + SendMessage(hwnd, WM_CLOSE, 0, 0); + } +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something + (void)timeout; + return false; // cannot kill +#else + ::kill(m_pid, SIGKILL); +#endif + stop(); + return true; +} + +#if _WIN32 +inline BOOL CALLBACK internal::executor::enum_windows_callback(HWND hwnd, LPARAM lParam) +{ + auto that = (executor *)lParam; + + DWORD pid; + auto tid = GetWindowThreadProcessId(hwnd, &pid); + if (tid == that->m_tid) + that->m_windows.insert(hwnd); + return TRUE; +} +#endif + +#if _WIN32 +inline void internal::executor::start_func(std::function const &fun) +{ + stop(); + + auto trampoline = [fun, this]() + { + // Save our thread id so that the caller can cancel us + m_tid = GetCurrentThreadId(); + EnumWindows(&enum_windows_callback, (LPARAM)this); + m_cond.notify_all(); + return fun(&m_exit_code); + }; + + std::unique_lock lock(m_mutex); + m_future = std::async(std::launch::async, trampoline); + m_cond.wait(lock); + m_running = true; +} + +#elif __EMSCRIPTEN__ +inline void internal::executor::start(int exit_code) +{ + m_exit_code = exit_code; +} + +#else +inline void internal::executor::start_process(std::vector const &command) +{ + stop(); + m_stdout.clear(); + m_exit_code = -1; + + int in[2], out[2]; + if (pipe(in) != 0 || pipe(out) != 0) + return; + + m_pid = fork(); + if (m_pid < 0) + return; + + close(in[m_pid ? 0 : 1]); + close(out[m_pid ? 1 : 0]); + + if (m_pid == 0) + { + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + + // Ignore stderr so that it doesn’t pollute the console (e.g. GTK+ errors from zenity) + int fd = open("/dev/null", O_WRONLY); + dup2(fd, STDERR_FILENO); + close(fd); + + std::vector args; + std::transform(command.cbegin(), command.cend(), std::back_inserter(args), + [](std::string const &s) { return const_cast(s.c_str()); }); + args.push_back(nullptr); // null-terminate argv[] + + execvp(args[0], args.data()); + exit(1); + } + + close(in[1]); + m_fd = out[0]; + auto flags = fcntl(m_fd, F_GETFL); + fcntl(m_fd, F_SETFL, flags | O_NONBLOCK); + + m_running = true; +} +#endif + +inline internal::executor::~executor() +{ + stop(); +} + +inline bool internal::executor::ready(int timeout /* = default_wait_timeout */) +{ + if (!m_running) + return true; + +#if _WIN32 + if (m_future.valid()) + { + auto status = m_future.wait_for(std::chrono::milliseconds(timeout)); + if (status != std::future_status::ready) + { + // On Windows, we need to run the message pump. If the async + // thread uses a Windows API dialog, it may be attached to the + // main thread and waiting for messages that only we can dispatch. + MSG msg; + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return false; + } + + m_stdout = m_future.get(); + } +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something + (void)timeout; +#else + char buf[BUFSIZ]; + ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore + if (received > 0) + { + m_stdout += std::string(buf, received); + return false; + } + + // Reap child process if it is dead. It is possible that the system has already reaped it + // (this happens when the calling application handles or ignores SIG_CHLD) and results in + // waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for + // a little while. + int status; + pid_t child = waitpid(m_pid, &status, WNOHANG); + if (child != m_pid && (child >= 0 || errno != ECHILD)) + { + // FIXME: this happens almost always at first iteration + std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); + return false; + } + + close(m_fd); + m_exit_code = WEXITSTATUS(status); +#endif + + m_running = false; + return true; +} + +inline void internal::executor::stop() +{ + // Loop until the user closes the dialog + while (!ready()) + ; +} + +// dll implementation + +#if _WIN32 +inline internal::platform::dll::dll(std::string const &name) + : handle(::LoadLibraryA(name.c_str())) +{} + +inline internal::platform::dll::~dll() +{ + if (handle) + ::FreeLibrary(handle); +} +#endif // _WIN32 + +// ole32_dll implementation + +#if _WIN32 +inline internal::platform::ole32_dll::ole32_dll() + : dll("ole32.dll") +{ + // Use COINIT_MULTITHREADED because COINIT_APARTMENTTHREADED causes crashes. + // See https://github.com/samhocevar/portable-file-dialogs/issues/51 + auto coinit = proc(*this, "CoInitializeEx"); + m_state = coinit(nullptr, COINIT_MULTITHREADED); +} + +inline internal::platform::ole32_dll::~ole32_dll() +{ + if (is_initialized()) + proc(*this, "CoUninitialize")(); +} + +inline bool internal::platform::ole32_dll::is_initialized() +{ + return m_state == S_OK || m_state == S_FALSE; +} +#endif + +// new_style_context implementation + +#if _WIN32 +inline internal::platform::new_style_context::new_style_context() +{ + // Only create one activation context for the whole app lifetime. + static HANDLE hctx = create(); + + if (hctx != INVALID_HANDLE_VALUE) + ActivateActCtx(hctx, &m_cookie); +} + +inline internal::platform::new_style_context::~new_style_context() +{ + DeactivateActCtx(0, m_cookie); +} + +inline HANDLE internal::platform::new_style_context::create() +{ + // This “hack” seems to be necessary for this code to work on windows XP. + // Without it, dialogs do not show and close immediately. GetError() + // returns 0 so I don’t know what causes this. I was not able to reproduce + // this behavior on Windows 7 and 10 but just in case, let it be here for + // those versions too. + // This hack is not required if other dialogs are used (they load comdlg32 + // automatically), only if message boxes are used. + dll comdlg32("comdlg32.dll"); + + // Using approach as shown here: https://stackoverflow.com/a/10444161 + UINT len = ::GetSystemDirectoryA(nullptr, 0); + std::string sys_dir(len, '\0'); + ::GetSystemDirectoryA(&sys_dir[0], len); + + ACTCTXA act_ctx = + { + // Do not set flag ACTCTX_FLAG_SET_PROCESS_DEFAULT, since it causes a + // crash with error “default context is already set”. + sizeof(act_ctx), + ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID, + "shell32.dll", 0, 0, sys_dir.c_str(), (LPCSTR)124, NULL, NULL + }; + + return ::CreateActCtxA(&act_ctx); +} +#endif // _WIN32 + +// dialog implementation + +inline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) const +{ + return m_async->ready(timeout); +} + +inline bool internal::dialog::kill() const +{ + return m_async->kill(); +} + +inline internal::dialog::dialog() + : m_async(std::make_shared()) +{ +} + +inline std::vector internal::dialog::desktop_helper() const +{ +#if __APPLE__ + return { "osascript" }; +#else + return { flags(flag::has_zenity) ? "zenity" + : flags(flag::has_matedialog) ? "matedialog" + : flags(flag::has_qarma) ? "qarma" + : flags(flag::has_kdialog) ? "kdialog" + : "echo" }; +#endif +} + +inline std::string internal::dialog::buttons_to_name(choice _choice) +{ + switch (_choice) + { + case choice::ok_cancel: return "okcancel"; + case choice::yes_no: return "yesno"; + case choice::yes_no_cancel: return "yesnocancel"; + case choice::retry_cancel: return "retrycancel"; + case choice::abort_retry_ignore: return "abortretryignore"; + /* case choice::ok: */ default: return "ok"; + } +} + +inline std::string internal::dialog::get_icon_name(icon _icon) +{ + switch (_icon) + { + case icon::warning: return "warning"; + case icon::error: return "error"; + case icon::question: return "question"; + // Zenity wants "information" but WinForms wants "info" + /* case icon::info: */ default: +#if _WIN32 + return "info"; +#else + return "information"; +#endif + } +} + +// THis is only used for debugging purposes +inline std::ostream& operator <<(std::ostream &s, std::vector const &v) +{ + int not_first = 0; + for (auto &e : v) + s << (not_first++ ? " " : "") << e; + return s; +} + +// Properly quote a string for Powershell: replace ' or " with '' or "" +// FIXME: we should probably get rid of newlines! +// FIXME: the \" sequence seems unsafe, too! +// XXX: this is no longer used but I would like to keep it around just in case +inline std::string internal::dialog::powershell_quote(std::string const &str) const +{ + return "'" + std::regex_replace(str, std::regex("['\"]"), "$&$&") + "'"; +} + +// Properly quote a string for osascript: replace \ or " with \\ or \" +// XXX: this also used to replace ' with \' when popen was used, but it would be +// smarter to do shell_quote(osascript_quote(...)) if this is needed again. +inline std::string internal::dialog::osascript_quote(std::string const &str) const +{ + return "\"" + std::regex_replace(str, std::regex("[\\\\\"]"), "\\$&") + "\""; +} + +// Properly quote a string for the shell: just replace ' with '\'' +// XXX: this is no longer used but I would like to keep it around just in case +inline std::string internal::dialog::shell_quote(std::string const &str) const +{ + return "'" + std::regex_replace(str, std::regex("'"), "'\\''") + "'"; +} + +// file_dialog implementation + +inline internal::file_dialog::file_dialog(type in_type, + std::string const &title, + std::string const &default_path /* = "" */, + std::vector const &filters /* = {} */, + opt options /* = opt::none */) +{ +#if _WIN32 + std::string filter_list; + std::regex whitespace(" *"); + for (size_t i = 0; i + 1 < filters.size(); i += 2) + { + filter_list += filters[i] + '\0'; + filter_list += std::regex_replace(filters[i + 1], whitespace, ";") + '\0'; + } + filter_list += '\0'; + + m_async->start_func([this, in_type, title, default_path, filter_list, + options](int *exit_code) -> std::string + { + (void)exit_code; + m_wtitle = internal::str2wstr(title); + m_wdefault_path = internal::str2wstr(default_path); + auto wfilter_list = internal::str2wstr(filter_list); + + // Initialise COM. This is required for the new folder selection window, + // (see https://github.com/samhocevar/portable-file-dialogs/pull/21) + // and to avoid random crashes with GetOpenFileNameW() (see + // https://github.com/samhocevar/portable-file-dialogs/issues/51) + ole32_dll ole32; + + // Folder selection uses a different method + if (in_type == type::folder) + { +#if PFD_HAS_IFILEDIALOG + if (flags(flag::is_vista)) + { + // On Vista and higher we should be able to use IFileDialog for folder selection + IFileDialog *ifd; + HRESULT hr = dll::proc(ole32, "CoCreateInstance") + (CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&ifd)); + + // In case CoCreateInstance fails (which it should not), try legacy approach + if (SUCCEEDED(hr)) + return select_folder_vista(ifd, options & opt::force_path); + } +#endif + + BROWSEINFOW bi; + memset(&bi, 0, sizeof(bi)); + + bi.lpfn = &bffcallback; + bi.lParam = (LPARAM)this; + + if (flags(flag::is_vista)) + { + if (ole32.is_initialized()) + bi.ulFlags |= BIF_NEWDIALOGSTYLE; + bi.ulFlags |= BIF_EDITBOX; + bi.ulFlags |= BIF_STATUSTEXT; + } + + auto *list = SHBrowseForFolderW(&bi); + std::string ret; + if (list) + { + auto buffer = new wchar_t[MAX_PATH]; + SHGetPathFromIDListW(list, buffer); + dll::proc(ole32, "CoTaskMemFree")(list); + ret = internal::wstr2str(buffer); + delete[] buffer; + } + return ret; + } + + OPENFILENAMEW ofn; + memset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(OPENFILENAMEW); + ofn.hwndOwner = GetActiveWindow(); + + ofn.lpstrFilter = wfilter_list.c_str(); + + auto woutput = std::wstring(MAX_PATH * 256, L'\0'); + ofn.lpstrFile = (LPWSTR)woutput.data(); + ofn.nMaxFile = (DWORD)woutput.size(); + if (!m_wdefault_path.empty()) + { + // If a directory was provided, use it as the initial directory. If + // a valid path was provided, use it as the initial file. Otherwise, + // let the Windows API decide. + auto path_attr = GetFileAttributesW(m_wdefault_path.c_str()); + if (path_attr != INVALID_FILE_ATTRIBUTES && (path_attr & FILE_ATTRIBUTE_DIRECTORY)) + ofn.lpstrInitialDir = m_wdefault_path.c_str(); + else if (m_wdefault_path.size() <= woutput.size()) + //second argument is size of buffer, not length of string + StringCchCopyW(ofn.lpstrFile, MAX_PATH*256+1, m_wdefault_path.c_str()); + else + { + ofn.lpstrFileTitle = (LPWSTR)m_wdefault_path.data(); + ofn.nMaxFileTitle = (DWORD)m_wdefault_path.size(); + } + } + ofn.lpstrTitle = m_wtitle.c_str(); + ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER; + + dll comdlg32("comdlg32.dll"); + + // Apply new visual style (required for windows XP) + new_style_context ctx; + + if (in_type == type::save) + { + if (!(options & opt::force_overwrite)) + ofn.Flags |= OFN_OVERWRITEPROMPT; + + dll::proc get_save_file_name(comdlg32, "GetSaveFileNameW"); + if (get_save_file_name(&ofn) == 0) + return ""; + return internal::wstr2str(woutput.c_str()); + } + else + { + if (options & opt::multiselect) + ofn.Flags |= OFN_ALLOWMULTISELECT; + ofn.Flags |= OFN_PATHMUSTEXIST; + + dll::proc get_open_file_name(comdlg32, "GetOpenFileNameW"); + if (get_open_file_name(&ofn) == 0) + return ""; + } + + std::string prefix; + for (wchar_t const *p = woutput.c_str(); *p; ) + { + auto filename = internal::wstr2str(p); + p += wcslen(p); + // In multiselect mode, we advance p one wchar further and + // check for another filename. If there is one and the + // prefix is empty, it means we just read the prefix. + if ((options & opt::multiselect) && *++p && prefix.empty()) + { + prefix = filename + "/"; + continue; + } + + m_vector_result.push_back(prefix + filename); + } + + return ""; + }); +#else + auto command = desktop_helper(); + + if (is_osascript()) + { + std::string script = "set ret to choose"; + switch (in_type) + { + case type::save: + script += " file name"; + break; + case type::open: default: + script += " file"; + if (options & opt::multiselect) + script += " with multiple selections allowed"; + break; + case type::folder: + script += " folder"; + break; + } + + if (default_path.size()) + script += " default location " + osascript_quote(default_path); + script += " with prompt " + osascript_quote(title); + + if (in_type == type::open) + { + // Concatenate all user-provided filter patterns + std::string patterns; + for (size_t i = 0; i < filters.size() / 2; ++i) + patterns += " " + filters[2 * i + 1]; + + // Split the pattern list to check whether "*" is in there; if it + // is, we have to disable filters because there is no mechanism in + // OS X for the user to override the filter. + std::regex sep("\\s+"); + std::string filter_list; + bool has_filter = true; + std::sregex_token_iterator iter(patterns.begin(), patterns.end(), sep, -1); + std::sregex_token_iterator end; + for ( ; iter != end; ++iter) + { + auto pat = iter->str(); + if (pat == "*" || pat == "*.*") + has_filter = false; + else if (internal::starts_with(pat, "*.")) + filter_list += (filter_list.size() == 0 ? "" : ",") + + osascript_quote(pat.substr(2, pat.size() - 2)); + } + if (has_filter && filter_list.size() > 0) + script += " of type {" + filter_list + "}"; + } + + if (in_type == type::open && (options & opt::multiselect)) + { + script += "\nset s to \"\""; + script += "\nrepeat with i in ret"; + script += "\n set s to s & (POSIX path of i) & \"\\n\""; + script += "\nend repeat"; + script += "\ncopy s to stdout"; + } + else + { + script += "\nPOSIX path of ret"; + } + + command.push_back("-e"); + command.push_back(script); + } + else if (is_zenity()) + { + command.push_back("--file-selection"); + command.push_back("--filename=" + default_path); + command.push_back("--title"); + command.push_back(title); + command.push_back("--separator=\n"); + + for (size_t i = 0; i < filters.size() / 2; ++i) + { + command.push_back("--file-filter"); + command.push_back(filters[2 * i] + "|" + filters[2 * i + 1]); + } + + if (in_type == type::save) + command.push_back("--save"); + if (in_type == type::folder) + command.push_back("--directory"); + if (!(options & opt::force_overwrite)) + command.push_back("--confirm-overwrite"); + if (options & opt::multiselect) + command.push_back("--multiple"); + } + else if (is_kdialog()) + { + switch (in_type) + { + case type::save: command.push_back("--getsavefilename"); break; + case type::open: command.push_back("--getopenfilename"); break; + case type::folder: command.push_back("--getexistingdirectory"); break; + } + if (options & opt::multiselect) + { + command.push_back("--multiple"); + command.push_back("--separate-output"); + } + + command.push_back(default_path); + + std::string filter; + for (size_t i = 0; i < filters.size() / 2; ++i) + filter += (i == 0 ? "" : " | ") + filters[2 * i] + "(" + filters[2 * i + 1] + ")"; + command.push_back(filter); + + command.push_back("--title"); + command.push_back(title); + } + + if (flags(flag::is_verbose)) + std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +inline std::string internal::file_dialog::string_result() +{ +#if _WIN32 + return m_async->result(); +#else + auto ret = m_async->result(); + // Strip potential trailing newline (zenity). Also strip trailing slash + // added by osascript for consistency with other backends. + while (!ret.empty() && (ret.back() == '\n' || ret.back() == '/')) + ret.pop_back(); + return ret; +#endif +} + +inline std::vector internal::file_dialog::vector_result() +{ +#if _WIN32 + m_async->result(); + return m_vector_result; +#else + std::vector ret; + auto result = m_async->result(); + for (;;) + { + // Split result along newline characters + auto i = result.find('\n'); + if (i == 0 || i == std::string::npos) + break; + ret.push_back(result.substr(0, i)); + result = result.substr(i + 1, result.size()); + } + return ret; +#endif +} + +#if _WIN32 +// Use a static function to pass as BFFCALLBACK for legacy folder select +inline int CALLBACK internal::file_dialog::bffcallback(HWND hwnd, UINT uMsg, + LPARAM, LPARAM pData) +{ + auto inst = (file_dialog *)pData; + switch (uMsg) + { + case BFFM_INITIALIZED: + SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)inst->m_wdefault_path.c_str()); + break; + } + return 0; +} + +#if PFD_HAS_IFILEDIALOG +inline std::string internal::file_dialog::select_folder_vista(IFileDialog *ifd, bool force_path) +{ + std::string result; + + IShellItem *folder; + + // Load library at runtime so app doesn't link it at load time (which will fail on windows XP) + dll shell32("shell32.dll"); + dll::proc + create_item(shell32, "SHCreateItemFromParsingName"); + + if (!create_item) + return ""; + + auto hr = create_item(m_wdefault_path.c_str(), + nullptr, + IID_PPV_ARGS(&folder)); + + // Set default folder if found. This only sets the default folder. If + // Windows has any info about the most recently selected folder, it + // will display it instead. Generally, calling SetFolder() to set the + // current directory “is not a good or expected user experience and + // should therefore be avoided”: + // https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder + if (SUCCEEDED(hr)) + { + if (force_path) + ifd->SetFolder(folder); + else + ifd->SetDefaultFolder(folder); + folder->Release(); + } + + // Set the dialog title and option to select folders + ifd->SetOptions(FOS_PICKFOLDERS); + ifd->SetTitle(m_wtitle.c_str()); + + hr = ifd->Show(GetActiveWindow()); + if (SUCCEEDED(hr)) + { + IShellItem* item; + hr = ifd->GetResult(&item); + if (SUCCEEDED(hr)) + { + wchar_t* wselected = nullptr; + item->GetDisplayName(SIGDN_FILESYSPATH, &wselected); + item->Release(); + + if (wselected) + { + result = internal::wstr2str(std::wstring(wselected)); + dll::proc(ole32_dll(), "CoTaskMemFree")(wselected); + } + } + } + + ifd->Release(); + + return result; +} +#endif +#endif + +// notify implementation + +inline notify::notify(std::string const &title, + std::string const &message, + icon _icon /* = icon::info */) +{ + if (_icon == icon::question) // Not supported by notifications + _icon = icon::info; + +#if _WIN32 + // Use a static shared pointer for notify_icon so that we can delete + // it whenever we need to display a new one, and we can also wait + // until the program has finished running. + struct notify_icon_data : public NOTIFYICONDATAW + { + ~notify_icon_data() { Shell_NotifyIconW(NIM_DELETE, this); } + }; + + static std::shared_ptr nid; + + // Release the previous notification icon, if any, and allocate a new + // one. Note that std::make_shared() does value initialization, so there + // is no need to memset the structure. + nid = nullptr; + nid = std::make_shared(); + + // For XP support + nid->cbSize = NOTIFYICONDATAW_V2_SIZE; + nid->hWnd = nullptr; + nid->uID = 0; + + // Flag Description: + // - NIF_ICON The hIcon member is valid. + // - NIF_MESSAGE The uCallbackMessage member is valid. + // - NIF_TIP The szTip member is valid. + // - NIF_STATE The dwState and dwStateMask members are valid. + // - NIF_INFO Use a balloon ToolTip instead of a standard ToolTip. The szInfo, uTimeout, szInfoTitle, and dwInfoFlags members are valid. + // - NIF_GUID Reserved. + nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_INFO; + + // Flag Description + // - NIIF_ERROR An error icon. + // - NIIF_INFO An information icon. + // - NIIF_NONE No icon. + // - NIIF_WARNING A warning icon. + // - NIIF_ICON_MASK Version 6.0. Reserved. + // - NIIF_NOSOUND Version 6.0. Do not play the associated sound. Applies only to balloon ToolTips + switch (_icon) + { + case icon::warning: nid->dwInfoFlags = NIIF_WARNING; break; + case icon::error: nid->dwInfoFlags = NIIF_ERROR; break; + /* case icon::info: */ default: nid->dwInfoFlags = NIIF_INFO; break; + } + + ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, LONG_PTR lParam) -> BOOL + { + ((NOTIFYICONDATAW *)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); + return false; + }; + + nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION); + ::EnumResourceNames(nullptr, RT_GROUP_ICON, icon_enum_callback, (LONG_PTR)nid.get()); + + nid->uTimeout = 5000; + + StringCchCopyW(nid->szInfoTitle, ARRAYSIZE(nid->szInfoTitle), internal::str2wstr(title).c_str()); + StringCchCopyW(nid->szInfo, ARRAYSIZE(nid->szInfo), internal::str2wstr(message).c_str()); + + // Display the new icon + Shell_NotifyIconW(NIM_ADD, nid.get()); +#else + auto command = desktop_helper(); + + if (is_osascript()) + { + command.push_back("-e"); + command.push_back("display notification " + osascript_quote(message) + + " with title " + osascript_quote(title)); + } + else if (is_zenity()) + { + command.push_back("--notification"); + command.push_back("--window-icon"); + command.push_back(get_icon_name(_icon)); + command.push_back("--text"); + command.push_back(title + "\n" + message); + } + else if (is_kdialog()) + { + command.push_back("--icon"); + command.push_back(get_icon_name(_icon)); + command.push_back("--title"); + command.push_back(title); + command.push_back("--passivepopup"); + command.push_back(message); + command.push_back("5"); + } + + if (flags(flag::is_verbose)) + std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +// message implementation + +inline message::message(std::string const &title, + std::string const &text, + choice _choice /* = choice::ok_cancel */, + icon _icon /* = icon::info */) +{ +#if _WIN32 + // Use MB_SYSTEMMODAL rather than MB_TOPMOST to ensure the message window is brought + // to front. See https://github.com/samhocevar/portable-file-dialogs/issues/52 + UINT style = MB_SYSTEMMODAL; + switch (_icon) + { + case icon::warning: style |= MB_ICONWARNING; break; + case icon::error: style |= MB_ICONERROR; break; + case icon::question: style |= MB_ICONQUESTION; break; + /* case icon::info: */ default: style |= MB_ICONINFORMATION; break; + } + + switch (_choice) + { + case choice::ok_cancel: style |= MB_OKCANCEL; break; + case choice::yes_no: style |= MB_YESNO; break; + case choice::yes_no_cancel: style |= MB_YESNOCANCEL; break; + case choice::retry_cancel: style |= MB_RETRYCANCEL; break; + case choice::abort_retry_ignore: style |= MB_ABORTRETRYIGNORE; break; + /* case choice::ok: */ default: style |= MB_OK; break; + } + + m_mappings[IDCANCEL] = button::cancel; + m_mappings[IDOK] = button::ok; + m_mappings[IDYES] = button::yes; + m_mappings[IDNO] = button::no; + m_mappings[IDABORT] = button::abort; + m_mappings[IDRETRY] = button::retry; + m_mappings[IDIGNORE] = button::ignore; + + m_async->start_func([text, title, style](int* exit_code) -> std::string + { + auto wtext = internal::str2wstr(text); + auto wtitle = internal::str2wstr(title); + // Apply new visual style (required for all Windows versions) + new_style_context ctx; + *exit_code = MessageBoxW(GetActiveWindow(), wtext.c_str(), wtitle.c_str(), style); + return ""; + }); + +#elif __EMSCRIPTEN__ + std::string full_message; + switch (_icon) + { + case icon::warning: full_message = "⚠️"; break; + case icon::error: full_message = "⛔"; break; + case icon::question: full_message = "❓"; break; + /* case icon::info: */ default: full_message = "ℹ"; break; + } + + full_message += ' ' + title + "\n\n" + text; + + // This does not really start an async task; it just passes the + // EM_ASM_INT return value to a fake start() function. + m_async->start(EM_ASM_INT( + { + if ($1) + return window.confirm(UTF8ToString($0)) ? 0 : -1; + alert(UTF8ToString($0)); + return 0; + }, full_message.c_str(), _choice == choice::ok_cancel)); +#else + auto command = desktop_helper(); + + if (is_osascript()) + { + std::string script = "display dialog " + osascript_quote(text) + + " with title " + osascript_quote(title); + switch (_choice) + { + case choice::ok_cancel: + script += "buttons {\"OK\", \"Cancel\"}" + " default button \"OK\"" + " cancel button \"Cancel\""; + m_mappings[256] = button::cancel; + break; + case choice::yes_no: + script += "buttons {\"Yes\", \"No\"}" + " default button \"Yes\"" + " cancel button \"No\""; + m_mappings[256] = button::no; + break; + case choice::yes_no_cancel: + script += "buttons {\"Yes\", \"No\", \"Cancel\"}" + " default button \"Yes\"" + " cancel button \"Cancel\""; + m_mappings[256] = button::cancel; + break; + case choice::retry_cancel: + script += "buttons {\"Retry\", \"Cancel\"}" + " default button \"Retry\"" + " cancel button \"Cancel\""; + m_mappings[256] = button::cancel; + break; + case choice::abort_retry_ignore: + script += "buttons {\"Abort\", \"Retry\", \"Ignore\"}" + " default button \"Retry\"" + " cancel button \"Retry\""; + m_mappings[256] = button::cancel; + break; + case choice::ok: default: + script += "buttons {\"OK\"}" + " default button \"OK\"" + " cancel button \"OK\""; + m_mappings[256] = button::ok; + break; + } + script += " with icon "; + switch (_icon) + { + #define PFD_OSX_ICON(n) "alias ((path to library folder from system domain) as text " \ + "& \"CoreServices:CoreTypes.bundle:Contents:Resources:" n ".icns\")" + case icon::info: default: script += PFD_OSX_ICON("ToolBarInfo"); break; + case icon::warning: script += "caution"; break; + case icon::error: script += "stop"; break; + case icon::question: script += PFD_OSX_ICON("GenericQuestionMarkIcon"); break; + #undef PFD_OSX_ICON + } + + command.push_back("-e"); + command.push_back(script); + } + else if (is_zenity()) + { + switch (_choice) + { + case choice::ok_cancel: + command.insert(command.end(), { "--question", "--cancel-label=Cancel", "--ok-label=OK" }); break; + case choice::yes_no: + // Do not use standard --question because it causes “No” to return -1, + // which is inconsistent with the “Yes/No/Cancel” mode below. + command.insert(command.end(), { "--question", "--switch", "--extra-button=No", "--extra-button=Yes" }); break; + case choice::yes_no_cancel: + command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=No", "--extra-button=Yes" }); break; + case choice::retry_cancel: + command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=Retry" }); break; + case choice::abort_retry_ignore: + command.insert(command.end(), { "--question", "--switch", "--extra-button=Ignore", "--extra-button=Abort", "--extra-button=Retry" }); break; + case choice::ok: + default: + switch (_icon) + { + case icon::error: command.push_back("--error"); break; + case icon::warning: command.push_back("--warning"); break; + default: command.push_back("--info"); break; + } + } + + command.insert(command.end(), { "--title", title, + "--width=300", "--height=0", // sensible defaults + "--text", text, + "--icon-name=dialog-" + get_icon_name(_icon) }); + } + else if (is_kdialog()) + { + if (_choice == choice::ok) + { + switch (_icon) + { + case icon::error: command.push_back("--error"); break; + case icon::warning: command.push_back("--sorry"); break; + default: command.push_back("--msgbox"); break; + } + } + else + { + std::string flag = "--"; + if (_icon == icon::warning || _icon == icon::error) + flag += "warning"; + flag += "yesno"; + if (_choice == choice::yes_no_cancel) + flag += "cancel"; + command.push_back(flag); + if (_choice == choice::yes_no || _choice == choice::yes_no_cancel) + { + m_mappings[0] = button::yes; + m_mappings[256] = button::no; + } + } + + command.push_back(text); + command.push_back("--title"); + command.push_back(title); + + // Must be after the above part + if (_choice == choice::ok_cancel) + command.insert(command.end(), { "--yes-label", "OK", "--no-label", "Cancel" }); + } + + if (flags(flag::is_verbose)) + std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +inline button message::result() +{ + int exit_code; + auto ret = m_async->result(&exit_code); + // osascript will say "button returned:Cancel\n" + // and others will just say "Cancel\n" + if (exit_code < 0 || // this means cancel + internal::ends_with(ret, "Cancel\n")) + return button::cancel; + if (internal::ends_with(ret, "OK\n")) + return button::ok; + if (internal::ends_with(ret, "Yes\n")) + return button::yes; + if (internal::ends_with(ret, "No\n")) + return button::no; + if (internal::ends_with(ret, "Abort\n")) + return button::abort; + if (internal::ends_with(ret, "Retry\n")) + return button::retry; + if (internal::ends_with(ret, "Ignore\n")) + return button::ignore; + if (m_mappings.count(exit_code) != 0) + return m_mappings[exit_code]; + return exit_code == 0 ? button::ok : button::cancel; +} + +// open_file implementation + +inline open_file::open_file(std::string const &title, + std::string const &default_path /* = "" */, + std::vector const &filters /* = { "All Files", "*" } */, + opt options /* = opt::none */) + : file_dialog(type::open, title, default_path, filters, options) +{ +} + +inline open_file::open_file(std::string const &title, + std::string const &default_path, + std::vector const &filters, + bool allow_multiselect) + : open_file(title, default_path, filters, + (allow_multiselect ? opt::multiselect : opt::none)) +{ +} + +inline std::vector open_file::result() +{ + return vector_result(); +} + +// save_file implementation + +inline save_file::save_file(std::string const &title, + std::string const &default_path /* = "" */, + std::vector const &filters /* = { "All Files", "*" } */, + opt options /* = opt::none */) + : file_dialog(type::save, title, default_path, filters, options) +{ +} + +inline save_file::save_file(std::string const &title, + std::string const &default_path, + std::vector const &filters, + bool confirm_overwrite) + : save_file(title, default_path, filters, + (confirm_overwrite ? opt::none : opt::force_overwrite)) +{ +} + +inline std::string save_file::result() +{ + return string_result(); +} + +// select_folder implementation + +inline select_folder::select_folder(std::string const &title, + std::string const &default_path /* = "" */, + opt options /* = opt::none */) + : file_dialog(type::folder, title, default_path, {}, options) +{ +} + +inline std::string select_folder::result() +{ + return string_result(); +} + +#endif // PFD_SKIP_IMPLEMENTATION + +} // namespace pfd + diff --git a/scripts/release-win32.sh b/scripts/release-win32.sh index 215575a2b..ddb9f806c 100755 --- a/scripts/release-win32.sh +++ b/scripts/release-win32.sh @@ -15,7 +15,7 @@ fi cd win32build # TODO: potential Arch-ism? -i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" -DBUILD_SHARED_LIBS=OFF .. || exit 1 +i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF .. || exit 1 make -j8 || exit 1 i686-w64-mingw32-strip -s furnace.exe || exit 1 diff --git a/scripts/release-win64.sh b/scripts/release-win64.sh index f0dfc4d2c..c02892e86 100755 --- a/scripts/release-win64.sh +++ b/scripts/release-win64.sh @@ -15,7 +15,7 @@ fi cd winbuild # TODO: potential Arch-ism? -x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" .. || exit 1 +x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" .. || exit 1 make -j8 || exit 1 x86_64-w64-mingw32-strip -s furnace.exe || exit 1 diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index 60435b016..f94c3fd5d 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -1,11 +1,7 @@ #include "fileDialog.h" #include "ImGuiFileDialog.h" -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wcast-function-type" -#endif - -#include "../../extern/pfd/portable-file-dialogs.h" +#include "../../extern/pfd-fixed/portable-file-dialogs.h" bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, String path, double dpiScale) { if (opened) return false; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fe53b9113..6d15ea089 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6581,6 +6581,9 @@ void FurnaceGUI::applyUISettings() { if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { logE("could not load big UI font!\n"); } + + if (fileDialog!=NULL) delete fileDialog; + fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); } bool FurnaceGUI::init() { diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index eb09e86fa..be0e979a8 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1091,9 +1091,6 @@ void FurnaceGUI::syncSettings() { decodeKeyMap(noteKeys,e->getConfString("noteKeys",DEFAULT_NOTE_KEYS)); parseKeybinds(); - - if (fileDialog!=NULL) delete fileDialog; - fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); } #define PUT_UI_COLOR(source) e->setConf(#source,(int)ImGui::GetColorU32(uiColors[source])); From 25eb3e4aae8e635bceba9314aa5cc06ccfe62cb3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 22:21:01 -0500 Subject: [PATCH 091/105] furnace II - the final fix --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2dd372d2b..4feccdf8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -398,7 +398,10 @@ if (APPLE) endif() if (NOT MSVC) - set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type) + set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + list(APPEND WARNING_FLAGS -Wno-cast-function-type) + endif() if (WARNINGS_ARE_ERRORS) list(APPEND WARNING_FLAGS -Werror) endif() From f56f4c80d1f461d81bf98cc9c7d2279be839ffc0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 23:10:43 -0500 Subject: [PATCH 092/105] GUI: move to system file dialog default setting will be selected by a poll --- src/gui/fileDialog.cpp | 32 ++++---- src/gui/fileDialog.h | 4 +- src/gui/gui.cpp | 162 ++++++++++++++++++++++++++++++++++------- 3 files changed, 151 insertions(+), 47 deletions(-) diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index f94c3fd5d..79a87f3ed 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -1,38 +1,34 @@ #include "fileDialog.h" #include "ImGuiFileDialog.h" +#include "../ta-log.h" #include "../../extern/pfd-fixed/portable-file-dialogs.h" -bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, String path, double dpiScale) { +bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale) { if (opened) return false; saving=false; curPath=path; if (sysDialog) { dialogO=new pfd::open_file(header,path,filter); } else { - String parsedFilter; - if (filter.size()&1) return false; - - for (size_t i=0; iDpiScale=dpiScale; - ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,parsedFilter.c_str(),path); + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path); } + opened=true; return true; } -bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, String path, double dpiScale) { +bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale) { + if (opened) return false; + saving=true; curPath=path; if (sysDialog) { - // TODO + dialogS=new pfd::save_file(header,path,filter); } else { - String parsedFilter; ImGuiFileDialog::Instance()->DpiScale=dpiScale; - ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,parsedFilter.c_str(),path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); } + opened=true; return true; } @@ -55,12 +51,12 @@ void FurnaceGUIFileDialog::close() { if (dialogO!=NULL) { delete dialogO; dialogO=NULL; - printf("deleting\n"); } } } else { ImGuiFileDialog::Instance()->Close(); } + opened=false; } bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { @@ -69,7 +65,7 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (dialogS!=NULL) { if (dialogS->ready(1)) { fileName=dialogS->result(); - printf("returning %s\n",fileName.c_str()); + logD("returning %s\n",fileName.c_str()); return true; } } @@ -78,10 +74,10 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (dialogO->ready(1)) { if (dialogO->result().empty()) { fileName=""; - printf("returning nothing\n"); + logD("returning nothing\n"); } else { fileName=dialogO->result()[0]; - printf("returning %s\n",fileName.c_str()); + logD("returning %s\n",fileName.c_str()); } return true; } diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h index 05ee87b40..b7f21abf0 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -16,8 +16,8 @@ class FurnaceGUIFileDialog { pfd::open_file* dialogO; pfd::save_file* dialogS; public: - bool openLoad(String header, std::vector filter, String path, double dpiScale); - bool openSave(String header, std::vector filter, String path, double dpiScale); + bool openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale); + bool openSave(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale); bool accepted(); void close(); bool render(const ImVec2& min, const ImVec2& max); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6d15ea089..c16b7d72e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4585,73 +4585,168 @@ bool dirExists(String what) { } void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { + bool hasOpened=false; switch (type) { case GUI_FILE_OPEN: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); - fileDialog->openLoad("Open File",{"compatible files", ".fur,.dmf", "all files", ".*"},workingDirSong,dpiScale); - //ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","compatible files{.fur,.dmf},.*",workingDirSong); + hasOpened=fileDialog->openLoad( + "Open File", + {"compatible files", "*.fur *.dmf", + "all files", ".*"}, + "compatible files{.fur,.dmf},.*", + workingDirSong, + dpiScale + ); break; case GUI_FILE_SAVE: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","Furnace song{.fur},DefleMask 1.1 module{.dmf}",workingDirSong,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Save File", + {"Furnace song", "*.fur", + "DefleMask 1.1 module", "*.dmf"}, + "Furnace song{.fur},DefleMask 1.1 module{.dmf}", + workingDirSong, + dpiScale + ); break; case GUI_FILE_SAVE_DMF_LEGACY: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","DefleMask 1.0/legacy module{.dmf}",workingDirSong,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Save File", + {"DefleMask 1.0/legacy module", "*.dmf"}, + "DefleMask 1.0/legacy module{.dmf}", + workingDirSong, + dpiScale + ); break; case GUI_FILE_INS_OPEN: if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Instrument","compatible files{.fui,.dmp,.tfi,.vgi},.*",workingDirIns); + hasOpened=fileDialog->openLoad( + "Load Instrument", + {"compatible files", "*.fui *.dmp *.tfi *.vgi", + "all files", ".*"}, + "compatible files{.fui,.dmp,.tfi,.vgi},.*", + workingDirIns, + dpiScale + ); break; case GUI_FILE_INS_SAVE: if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Instrument","Furnace instrument{.fui}",workingDirIns,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Save Instrument", + {"Furnace instrument", "*.fui"}, + "Furnace instrument{.fui}", + workingDirIns, + dpiScale + ); break; case GUI_FILE_WAVE_OPEN: if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Wavetable","compatible files{.fuw,.dmw},.*",workingDirWave); + hasOpened=fileDialog->openLoad( + "Load Wavetable", + {"compatible files", "*.fuw *.dmw", + "all files", ".*"}, + "compatible files{.fuw,.dmw},.*", + workingDirWave, + dpiScale + ); break; case GUI_FILE_WAVE_SAVE: if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Wavetable","Furnace wavetable{.fuw}",workingDirWave,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Save Wavetable", + {"Furnace wavetable", ".fuw"}, + "Furnace wavetable{.fuw}", + workingDirWave, + dpiScale + ); break; case GUI_FILE_SAMPLE_OPEN: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Sample","Wave file{.wav},.*",workingDirSample); + hasOpened=fileDialog->openLoad( + "Load Sample", + {"Wave file", "*.wav", + "all files", ".*"}, + "Wave file{.wav},.*", + workingDirSample, + dpiScale + ); break; case GUI_FILE_SAMPLE_SAVE: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Sample","Wave file{.wav}",workingDirSample,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Save Sample", + {"Wave file", "*.wav"}, + "Wave file{.wav}", + workingDirSample, + dpiScale + ); break; case GUI_FILE_EXPORT_AUDIO_ONE: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDirAudioExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Export Audio", + {"Wave file", "*.wav"}, + "Wave file{.wav}", + workingDirAudioExport, + dpiScale + ); break; case GUI_FILE_EXPORT_AUDIO_PER_SYS: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDirAudioExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Export Audio", + {"Wave file", "*.wav"}, + "Wave file{.wav}", + workingDirAudioExport, + dpiScale + ); break; case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDirAudioExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Export Audio", + {"Wave file", "*.wav"}, + "Wave file{.wav}", + workingDirAudioExport, + dpiScale + ); break; case GUI_FILE_EXPORT_VGM: if (!dirExists(workingDirVGMExport)) workingDirVGMExport=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export VGM",".vgm",workingDirVGMExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Export VGM", + {"VGM file", "*.vgm"}, + "VGM file{.vgm}", + workingDirVGMExport, + dpiScale + ); break; case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; case GUI_FILE_LOAD_MAIN_FONT: if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Select Font","compatible files{.ttf,.otf,.ttc}",workingDirFont); + hasOpened=fileDialog->openLoad( + "Select Font", + {"compatible files", "*.ttf *.otf *.ttc"}, + "compatible files{.ttf,.otf,.ttc}", + workingDirFont, + dpiScale + ); break; case GUI_FILE_LOAD_PAT_FONT: if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Select Font","compatible files{.ttf,.otf,.ttc}",workingDirFont); + hasOpened=fileDialog->openLoad( + "Select Font", + {"compatible files", "*.ttf *.otf *.ttc"}, + "compatible files{.ttf,.otf,.ttc}", + workingDirFont, + dpiScale + ); break; } - curFileDialog=type; + if (hasOpened) curFileDialog=type; //ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; } @@ -4776,7 +4871,7 @@ int FurnaceGUI::load(String path) { } if (len<1) { if (len==0) { - printf("that file is empty!\n"); + logE("that file is empty!\n"); lastError="file is empty"; } else { perror("tell error"); @@ -4932,6 +5027,15 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { fileName+=x; \ } +#define checkExtensionDual(x,y,fallback) \ + String lowerCase=fileName; \ + for (char& i: lowerCase) { \ + if (i>='A' && i<='Z') i+='a'-'A'; \ + } \ + if (lowerCase.size()<4 || (lowerCase.rfind(x)!=lowerCase.size()-4 && lowerCase.rfind(y)!=lowerCase.size()-4)) { \ + fileName+=fallback; \ + } + #define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str() void FurnaceGUI::editOptions(bool topMenu) { @@ -5980,11 +6084,9 @@ bool FurnaceGUI::loop() { fileName=fileDialog->getFileName(); if (fileName!="") { if (curFileDialog==GUI_FILE_SAVE) { - if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song") { - checkExtension(".fur"); - } else { - checkExtension(".dmf"); - } + // we can't tell whether the user chose .dmf or .fur in the system file picker + const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song")?".fur":".dmf"; + checkExtensionDual(".fur",".dmf",fallbackExt); } if (curFileDialog==GUI_FILE_SAVE_DMF_LEGACY) { checkExtension(".dmf"); @@ -6011,9 +6113,13 @@ bool FurnaceGUI::loop() { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); } break; - case GUI_FILE_SAVE: - printf("saving: %s\n",copyOfName.c_str()); - if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song") { + case GUI_FILE_SAVE: { + logD("saving: %s\n",copyOfName.c_str()); + String lowerCase=fileName; + for (char& i: lowerCase) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + if ((lowerCase.size()<4 || lowerCase.rfind(".dmf")!=lowerCase.size()-4)) { if (save(copyOfName,0)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } @@ -6023,8 +6129,9 @@ bool FurnaceGUI::loop() { } } break; + } case GUI_FILE_SAVE_DMF_LEGACY: - printf("saving: %s\n",copyOfName.c_str()); + logD("saving: %s\n",copyOfName.c_str()); if (save(copyOfName,24)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } @@ -6713,6 +6820,7 @@ bool FurnaceGUI::init() { ImGui::GetIO().IniFilename=finalLayoutPath; ImGui::LoadIniSettingsFromDisk(finalLayoutPath); + // TODO: allow changing these colors. ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir,"",ImVec4(0.0f,1.0f,1.0f,1.0f),ICON_FA_FOLDER_O); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile,"",ImVec4(0.7f,0.7f,0.7f,1.0f),ICON_FA_FILE_O); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fur",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); From e1eee0540e8b860f5296f1d06b7fa0eb6ff8aeb7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 23:10:52 -0500 Subject: [PATCH 093/105] update readme to add CI badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e1d97726e..a1b648540 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ some people have provided packages for Unix/Unix-like distributions. here's a li # developer info +[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml) + **NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building.** ## dependencies From 4ba6058b03a57f370f6d34a6ce44483336259cda Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 23:24:57 -0500 Subject: [PATCH 094/105] GUI: system file dialog on by default in order to make a test build --- src/gui/gui.h | 2 +- src/gui/settings.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index 9bf68e529..9e5585475 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -587,7 +587,7 @@ class FurnaceGUI { insFocusesPattern(1), stepOnInsert(0), unifiedDataView(0), - sysFileDialog(0), + sysFileDialog(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index be0e979a8..98724bc59 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -898,7 +898,7 @@ void FurnaceGUI::syncSettings() { settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); settings.stepOnInsert=e->getConfInt("stepOnInsert",0); settings.unifiedDataView=e->getConfInt("unifiedDataView",0); - settings.sysFileDialog=e->getConfInt("sysFileDialog",0); + settings.sysFileDialog=e->getConfInt("sysFileDialog",1); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); From 71702ee59555c9d9e5149d09674a46a99c28c7d8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 23:37:12 -0500 Subject: [PATCH 095/105] update release scripts --- scripts/release-win32.sh | 4 ++++ scripts/release-win64.sh | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/scripts/release-win32.sh b/scripts/release-win32.sh index ddb9f806c..2257a595e 100755 --- a/scripts/release-win32.sh +++ b/scripts/release-win32.sh @@ -31,3 +31,7 @@ cp -r ../../papers papers || exit 1 cp -r ../../demos demos || exit 1 zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos + +furName=$(git describe --tags | sed "s/v0/0/") + +mv furnace.zip furnace-"$furName"-win32.zip diff --git a/scripts/release-win64.sh b/scripts/release-win64.sh index c02892e86..db580e093 100755 --- a/scripts/release-win64.sh +++ b/scripts/release-win64.sh @@ -31,3 +31,7 @@ cp -r ../../papers papers || exit 1 cp -r ../../demos demos || exit 1 zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos + +furName=$(git describe --tags | sed "s/v0/0/") + +mv furnace.zip furnace-"$furName"-win64.zip From 394a440f3d8085b366270ef205f708d4ba6ebe8d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 23:37:25 -0500 Subject: [PATCH 096/105] prepare for unified ins/wave/sample list --- src/gui/settings.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 98724bc59..a10795fb5 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -408,6 +408,11 @@ void FurnaceGUI::drawSettings() { settings.macroView=macroViewB; } + bool unifiedDataViewB=settings.unifiedDataView; + if (ImGui::Checkbox("Unified instrument/wavetable/sample list",&unifiedDataViewB)) { + settings.unifiedDataView=unifiedDataViewB; + } + bool chipNamesB=settings.chipNames; if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) { settings.chipNames=chipNamesB; From 5b2ec3ee8791825a434c7e2cf47e5ce56bee1373 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 00:26:06 -0500 Subject: [PATCH 097/105] NES: fix slide up fixes #208 --- src/engine/platform/nes.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 897d8e842..06fbc725c 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -201,6 +201,7 @@ void DivPlatformNES::tick() { } else { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; if (chan[i].freq>2047) chan[i].freq=2047; + if (chan[i].freq<0) chan[i].freq=0; } if (chan[i].keyOn) { //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); From a96fd5727e75feb54e6e44b454f1f08c17cd498a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 00:26:27 -0500 Subject: [PATCH 098/105] GUI: begin working on unified ins/wave/sample list --- src/gui/gui.cpp | 88 +++++++++++++++++++++++++++++++++++--------- src/gui/gui.h | 3 ++ src/gui/guiConst.cpp | 29 +++++++++++++++ src/gui/guiConst.h | 1 + src/gui/insEdit.cpp | 50 +------------------------ 5 files changed, 104 insertions(+), 67 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index c16b7d72e..3143f6843 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -32,6 +32,7 @@ #include "ImGuiFileDialog.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" +#include "plot_nolerp.h" #include "guiConst.h" #include "intConst.h" #include @@ -1090,6 +1091,13 @@ void FurnaceGUI::drawInsList() { } ImGui::Separator(); if (ImGui::BeginTable("InsListScroll",1,ImGuiTableFlags_ScrollY)) { + if (settings.unifiedDataView) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(ICON_FA_TASKS " Instruments"); + ImGui::Indent(); + } + for (int i=0; i<(int)e->song.ins.size(); i++) { DivInstrument* ins=e->song.ins[i]; String name; @@ -1214,12 +1222,32 @@ void FurnaceGUI::drawInsList() { } ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s",(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]); if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { insEditOpen=true; nextWindow=GUI_WINDOW_INS_EDIT; } } } + + if (settings.unifiedDataView) { + ImGui::Unindent(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(ICON_FA_AREA_CHART " Wavetables"); + ImGui::Indent(); + actualWaveList(); + ImGui::Unindent(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(ICON_FA_VOLUME_UP " Samples"); + ImGui::Indent(); + actualSampleList(); + ImGui::Unindent(); + } + ImGui::EndTable(); } } @@ -1231,6 +1259,47 @@ const char* sampleNote[12]={ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; + +void FurnaceGUI::actualWaveList() { + float wavePreview[256]; + for (int i=0; i<(int)e->song.wave.size(); i++) { + DivWavetable* wave=e->song.wave[i]; + for (int i=0; ilen; i++) { + wavePreview[i]=wave->data[i]; + } + if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(fmt::sprintf("%d##_WAVE%d\n",i,i).c_str(),curWave==i)) { + curWave=i; + } + if (ImGui::IsItemHovered()) { + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + waveEditOpen=true; + } + } + ImGui::SameLine(); + PlotNoLerp(fmt::sprintf("##_WAVEP%d",i).c_str(),wavePreview,wave->len+1,0,NULL,0,wave->max); + } +} + +void FurnaceGUI::actualSampleList() { + for (int i=0; i<(int)e->song.sample.size(); i++) { + DivSample* sample=e->song.sample[i]; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) { + curSample=i; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Bank %d: %s",i/12,sampleNote[i%12]); + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + sampleEditOpen=true; + } + } + } +} + void FurnaceGUI::drawSampleList() { if (nextWindow==GUI_WINDOW_SAMPLE_LIST) { sampleListOpen=true; @@ -1272,24 +1341,7 @@ void FurnaceGUI::drawSampleList() { } ImGui::Separator(); if (ImGui::BeginTable("SampleListScroll",1,ImGuiTableFlags_ScrollY)) { - for (int i=0; i<(int)e->song.sample.size(); i++) { - DivSample* sample=e->song.sample[i]; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if ((i%12)==0) { - if (i>0) ImGui::Unindent(); - ImGui::Text("Bank %d",i/12); - ImGui::Indent(); - } - if (ImGui::Selectable(fmt::sprintf("%s: %s##_SAM%d",sampleNote[i%12],sample->name,i).c_str(),curSample==i)) { - curSample=i; - } - if (ImGui::IsItemHovered()) { - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - sampleEditOpen=true; - } - } - } + actualSampleList(); ImGui::EndTable(); } ImGui::Unindent(); diff --git a/src/gui/gui.h b/src/gui/gui.h index 9e5585475..22e8036e0 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -721,6 +721,9 @@ class FurnaceGUI { void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord); + void actualWaveList(); + void actualSampleList(); + void drawEditControls(); void drawSongInfo(); void drawOrders(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index c872b05bd..83220d252 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -19,6 +19,7 @@ // guiConst: constants used in the GUI like arrays, strings and other stuff #include "guiConst.h" +#include "../engine/instrument.h" const int opOrder[4]={ 0, 2, 1, 3 @@ -64,3 +65,31 @@ const char* pitchLabel[11]={ "1/6", "1/5", "1/4", "1/3", "1/2", "1x", "2x", "3x", "4x", "5x", "6x" }; +const char* insTypes[DIV_INS_MAX]={ + "Standard", + "FM (4-operator)", + "Game Boy", + "C64", + "Amiga/Sample", + "PC Engine", + "AY-3-8910/SSG", + "AY8930", + "TIA", + "SAA1099", + "VIC", + "PET", + "VRC6", + "FM (OPLL)", + "FM (OPL)", + "FDS", + "Virtual Boy", + "Namco 163", + "Konami SCC", + "FM (OPZ)", + "POKEY", + "PC Beeper", + "WonderSwan", + "Atari Lynx", + "VERA", + "X1-010" +}; \ No newline at end of file diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 3d252b536..9f4159159 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -23,3 +23,4 @@ extern const int opOrder[4]; extern const char* noteNames[180]; extern const char* noteNamesG[180]; extern const char* pitchLabel[11]; +extern const char* insTypes[]; \ No newline at end of file diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index e653b097f..828eced3a 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,35 +27,6 @@ #include #include "plot_nolerp.h" -const char* insTypes[DIV_INS_MAX]={ - "Standard", - "FM (4-operator)", - "Game Boy", - "C64", - "Amiga/Sample", - "PC Engine", - "AY-3-8910/SSG", - "AY8930", - "TIA", - "SAA1099", - "VIC", - "PET", - "VRC6", - "FM (OPLL)", - "FM (OPL)", - "FDS", - "Virtual Boy", - "Namco 163", - "Konami SCC", - "FM (OPZ)", - "POKEY", - "PC Beeper", - "WonderSwan", - "Atari Lynx", - "VERA", - "X1-010" -}; - const char* ssgEnvTypes[8]={ "Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN" }; @@ -1787,7 +1758,6 @@ void FurnaceGUI::drawWaveList() { nextWindow=GUI_WINDOW_NOTHING; } if (!waveListOpen) return; - float wavePreview[256]; if (ImGui::Begin("Wavetables",&waveListOpen)) { if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) { doAction(GUI_ACTION_WAVE_LIST_ADD); @@ -1818,25 +1788,7 @@ void FurnaceGUI::drawWaveList() { } ImGui::Separator(); if (ImGui::BeginTable("WaveListScroll",1,ImGuiTableFlags_ScrollY)) { - for (int i=0; i<(int)e->song.wave.size(); i++) { - DivWavetable* wave=e->song.wave[i]; - for (int i=0; ilen; i++) { - wavePreview[i]=wave->data[i]; - } - if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(fmt::sprintf("%d##_WAVE%d\n",i,i).c_str(),curWave==i)) { - curWave=i; - } - if (ImGui::IsItemHovered()) { - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - waveEditOpen=true; - } - } - ImGui::SameLine(); - PlotNoLerp(fmt::sprintf("##_WAVEP%d",i).c_str(),wavePreview,wave->len+1,0,NULL,0,wave->max); - } + actualWaveList(); ImGui::EndTable(); } } From e009fc64f2a58cbea6c8ee8093bd61998ee3ddd9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 01:23:31 -0500 Subject: [PATCH 099/105] allow instrument change during slides --- papers/format.md | 4 +++- src/engine/engine.h | 7 ++++--- src/engine/fileOps.cpp | 14 ++++++++++++-- src/engine/playback.cpp | 6 ++++++ src/engine/song.h | 4 +++- src/gui/gui.cpp | 4 ++++ 6 files changed, 32 insertions(+), 7 deletions(-) diff --git a/papers/format.md b/papers/format.md index b04deac74..d5b9c67e6 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 66: Furnace dev66 - 65: Furnace dev65 - 64: Furnace dev64 - 63: Furnace dev63 @@ -207,7 +208,8 @@ size | description 1 | continuous vibrato (>=62) or reserved 1 | broken DAC mode (>=64) or reserved 1 | one tick cut (>=65) or reserved - 2 | reserved + 1 | instrument change allowed during porta (>=66) or reserved + 1 | reserved 4?? | pointers to instruments 4?? | pointers to wavetables 4?? | pointers to samples diff --git a/src/engine/engine.h b/src/engine/engine.h index fc70c8730..b75fc2f07 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev65" -#define DIV_ENGINE_VERSION 65 +#define DIV_VERSION "dev66" +#define DIV_ENGINE_VERSION 66 enum DivStatusView { DIV_STATUS_NOTHING=0, @@ -69,7 +69,7 @@ enum DivHaltPositions { struct DivChannelState { std::vector delayed; - int note, oldNote, pitch, portaSpeed, portaNote; + int note, oldNote, lastIns, pitch, portaSpeed, portaNote; int volume, volSpeed, cut, rowDelay, volMax; int delayOrder, delayRow, retrigSpeed, retrigTick; int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine; @@ -80,6 +80,7 @@ struct DivChannelState { DivChannelState(): note(-1), oldNote(-1), + lastIns(-1), pitch(0), portaSpeed(-1), portaNote(-1), diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 3c1a9d2a1..524531f85 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -142,6 +142,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.ignoreDuplicateSlides=true; ds.brokenDACMode=true; ds.oneTickCut=false; + ds.newInsTriggersInPorta=true; // 1.1 compat flags if (ds.version>24) { @@ -807,6 +808,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<65) { ds.oneTickCut=false; } + if (ds.version<66) { + ds.newInsTriggersInPorta=false; + } ds.isDMF=false; reader.readS(); // reserved @@ -993,7 +997,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<2; i++) reader.readC(); + if (ds.version>=66) { + ds.newInsTriggersInPorta=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<1; i++) reader.readC(); } else { for (int i=0; i<20; i++) reader.readC(); } @@ -1437,7 +1446,8 @@ SafeWriter* DivEngine::saveFur() { w->writeC(song.continuousVibrato); w->writeC(song.brokenDACMode); w->writeC(song.oneTickCut); - for (int i=0; i<2; i++) { + w->writeC(song.newInsTriggersInPorta); + for (int i=0; i<1; i++) { w->writeC(0); } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 4626635b0..8a543fee2 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -654,6 +654,12 @@ void DivEngine::processRow(int i, bool afterDelay) { // instrument if (pat->data[whatRow][2]!=-1) { dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2])); + if (chan[i].lastIns!=pat->data[whatRow][2]) { + chan[i].lastIns=pat->data[whatRow][2]; + if (chan[i].inPorta && song.newInsTriggersInPorta) { + dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL)); + } + } } // note if (pat->data[whatRow][0]==100) { // note off diff --git a/src/engine/song.h b/src/engine/song.h index 5747aa511..7fc6ec7fd 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -297,6 +297,7 @@ struct DivSong { bool continuousVibrato; bool brokenDACMode; bool oneTickCut; + bool newInsTriggersInPorta; DivOrders orders; std::vector ins; @@ -363,7 +364,8 @@ struct DivSong { stopPortaOnNoteOff(false), continuousVibrato(false), brokenDACMode(false), - oneTickCut(false) { + oneTickCut(false), + newInsTriggersInPorta(true) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 3143f6843..24b7d46be 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2216,6 +2216,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6"); } + ImGui::Checkbox("Allow instrument change during slides",&e->song.newInsTriggersInPorta); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; ImGui::End(); From 67d516fcee95ffd21843bca1d5c821f8afa9b4e7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 01:38:11 -0500 Subject: [PATCH 100/105] Genesis: prioritize DAC writes --- src/engine/platform/genesis.cpp | 10 +++++----- src/engine/platform/genesis.h | 4 ++-- src/engine/platform/genesisshared.h | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index b3096e72a..75c54af68 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -92,7 +92,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s DivSample* s=parent->getSample(dacSample); if (s->samples>0) { if (!isMuted[5]) { - immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); } if (++dacPos>=s->samples) { if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { @@ -121,7 +121,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s //printf("write: %x = %.2x\n",w.addr,w.val); lastBusy=0; regPool[w.addr&0x1ff]=w.val; - writes.pop(); + writes.pop_front(); } else { lastBusy++; if (fm.write_busy==0) { @@ -159,7 +159,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si DivSample* s=parent->getSample(dacSample); if (s->samples>0) { if (!isMuted[5]) { - immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); } if (++dacPos>=s->samples) { if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { @@ -184,7 +184,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr); fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0x1ff]=w.val; - writes.pop(); + writes.pop_front(); lastBusy=1; } @@ -782,7 +782,7 @@ int DivPlatformGenesis::getRegisterPoolSize() { } void DivPlatformGenesis::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,512); if (useYMFM) { fm_ymfm->reset(); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index cd7b0396c..095744dfe 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -20,7 +20,7 @@ #ifndef _GENESIS_H #define _GENESIS_H #include "../dispatch.h" -#include +#include #include "../../../extern/Nuked-OPN2/ym3438.h" #include "sound/ymfm/ymfm_opn.h" @@ -68,7 +68,7 @@ class DivPlatformGenesis: public DivDispatch { bool addrOrVal; QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; - std::queue writes; + std::deque writes; ym3438_t fm; int delay; unsigned char lastBusy; diff --git a/src/engine/platform/genesisshared.h b/src/engine/platform/genesisshared.h index d2402a60d..01d45f955 100644 --- a/src/engine/platform/genesisshared.h +++ b/src/engine/platform/genesisshared.h @@ -43,6 +43,7 @@ static int orderedOps[4]={ }; #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } +#define urgentWrite(a,v) if (!skipRegisterWrites) {if (writes.front().addrOrVal) {writes.push_back(QueuedWrite(a,v));} else {writes.push_front(QueuedWrite(a,v));}; if (dumpWrites) {addWrite(a,v);} } #include "fmshared_OPN.h" From df5c1ae8596a05a7e500020dcd9015de3647e5aa Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 01:54:31 -0500 Subject: [PATCH 101/105] OPL: finally fix that order issue (kind of) --- src/engine/platform/opl.cpp | 57 ++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index d84265530..ffe4435dc 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -456,7 +456,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].insChanged) { - int ops=(slots[3][c.chan]!=255 && ins->fm.ops==4 && oplType==3)?4:2; + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; for (int i=0; i1) { + rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3)); + } } - rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); - if (chan[i].active) { - chan[i].keyOn=true; - chan[i].freqChanged=true; + + if (isMuted[i]) { + rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)); + if (ops==4) { + rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)); + } + } else { + rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)|((chan[i].pan&3)<<4)); + if (ops==4) { + rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)|((chan[i].pan&3)<<4)); + } } } - if (dacMode) { - rWrite(0x2b,0x80); - } - immWrite(0x22,lfoValue); - */ } void DivPlatformOPL::toggleRegisterDump(bool enable) { From 32581bb228171f8e65aea834b2196dc4169a115a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 01:57:46 -0500 Subject: [PATCH 102/105] OPL: volume --- src/engine/platform/opl.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index ffe4435dc..d453dab2b 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -524,21 +524,23 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (!chan[c.chan].std.hasVol) { chan[c.chan].outVol=c.value; } - /* - for (int i=0; i<4; i++) { - unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; - DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + for (int i=0; i Date: Mon, 14 Mar 2022 02:30:25 -0500 Subject: [PATCH 103/105] OPL: kind of fix 4-op mode --- src/engine/platform/opl.cpp | 57 ++++++++++++++++++++++++++++++------- src/engine/platform/opl.h | 5 ++-- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index d453dab2b..fe446ad23 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -349,10 +349,21 @@ void DivPlatformOPL::tick() { if (chan[i].keyOn || chan[i].keyOff) { immWrite(chanMap[i]+ADDR_FREQH,0x00|(chan[i].freqH&31)); + if (chan[i].state.ops==4 && i<6) { + immWrite(chanMap[i+1]+ADDR_FREQH,0x00|(chan[i].freqH&31)); + } chan[i].keyOff=false; } } + if (update4OpMask) { + update4OpMask=false; + if (oplType==3) { + unsigned char opMask=chan[0].fourOp|(chan[2].fourOp<<1)|(chan[4].fourOp<<2)|(chan[6].fourOp<<3)|(chan[8].fourOp<<4)|(chan[10].fourOp<<5); + rWrite(0x104,opMask); + } + } + for (int i=0; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { immWrite(i,pendingWrites[i]&0xff); @@ -368,12 +379,21 @@ void DivPlatformOPL::tick() { chan[i].freqH=freqt>>8; chan[i].freqL=freqt&0xff; immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL); + if (chan[i].state.ops==4 && i<6) { + immWrite(chanMap[i+1]+ADDR_FREQ,chan[i].freqL); + } } if (chan[i].keyOn) { immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(0x20)); + if (chan[i].state.ops==4 && i<6) { + immWrite(chanMap[i+1]+ADDR_FREQH,chan[i].freqH|(0x20)); + } chan[i].keyOn=false; } else if (chan[i].freqChanged) { immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5)); + if (chan[i].state.ops==4 && i<6) { + immWrite(chanMap[i+1]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5)); + } } chan[i].freqChanged=false; } @@ -457,6 +477,8 @@ int DivPlatformOPL::dispatch(DivCommand c) { } if (chan[c.chan].insChanged) { int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + chan[c.chan].fourOp=(ops==4); + update4OpMask=true; for (int i=0; i0)<<1)|((c.value>>4)>0); + } + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (isMuted[c.chan]) { + rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)); + if (ops==4) { + rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)); + } + } else { + rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); + if (ops==4) { + rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); + } } //rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); break; @@ -687,6 +715,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { void DivPlatformOPL::forceIns() { for (int i=0; i<18; i++) { int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2; + chan[i].fourOp=(ops==4); for (int j=0; j Date: Mon, 14 Mar 2022 02:39:10 -0500 Subject: [PATCH 104/105] fix 4-op mode for real --- src/engine/platform/opl.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index fe446ad23..7fe8bca92 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -360,7 +360,8 @@ void DivPlatformOPL::tick() { update4OpMask=false; if (oplType==3) { unsigned char opMask=chan[0].fourOp|(chan[2].fourOp<<1)|(chan[4].fourOp<<2)|(chan[6].fourOp<<3)|(chan[8].fourOp<<4)|(chan[10].fourOp<<5); - rWrite(0x104,opMask); + immWrite(0x104,opMask); + printf("updating opMask to %.2x\n",opMask); } } From 714d189b57abc2deb78cbb98c97031f1d23c92e2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 02:47:04 -0500 Subject: [PATCH 105/105] OPL: more work and channel muting --- src/engine/platform/opl.cpp | 55 ++++++++++++++++++++++++++----------- src/engine/platform/opl.h | 2 +- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 7fe8bca92..84e8dd260 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -222,7 +222,7 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformOPL::tick() { - for (int i=0; i<20; i++) { + for (int i=0; icalcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); if (chan[i].freq>131071) chan[i].freq=131071; @@ -445,25 +445,42 @@ int DivPlatformOPL::toFreq(int freq) { void DivPlatformOPL::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - /* - for (int j=0; j<4; j++) { - unsigned short baseAddr=chanOffs[ch]|opOffs[j]; - DivInstrumentFM::Operator& op=chan[ch].state.op[j]; + int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2; + chan[ch].fourOp=(ops==4); + update4OpMask=true; + for (int i=0; i>1)&1)|(chan[ch].state.fb<<1)); + } + } else { + rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&3)<<4)); + if (ops==4) { + rWrite(chanMap[ch+1]+ADDR_LR_FB_ALG,((chan[ch].state.alg>>1)&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&3)<<4)); + } + } } int DivPlatformOPL::dispatch(DivCommand c) { + // TODO: drums mode! + if (c.chan>=melodicChans) return 0; switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); @@ -714,7 +731,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { } void DivPlatformOPL::forceIns() { - for (int i=0; i<18; i++) { + for (int i=0; i