From a3fc76c984405f177bdef2dfd78cc4f8cb760e7b Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 10 May 2022 11:18:25 +0700 Subject: [PATCH 01/68] Add SCC and SCC+ support --- CMakeLists.txt | 3 + src/engine/dispatchContainer.cpp | 9 + src/engine/platform/scc.cpp | 387 ++++++++++++++++ src/engine/platform/scc.h | 91 ++++ src/engine/platform/sound/scc/scc.cpp | 612 ++++++++++++++++++++++++++ src/engine/platform/sound/scc/scc.hpp | 136 ++++++ src/gui/guiConst.cpp | 2 + src/gui/presets.cpp | 14 + 8 files changed, 1254 insertions(+) create mode 100644 src/engine/platform/scc.cpp create mode 100644 src/engine/platform/scc.h create mode 100644 src/engine/platform/sound/scc/scc.cpp create mode 100644 src/engine/platform/sound/scc/scc.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f6b807dd4..db0a51a5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -310,6 +310,8 @@ src/engine/platform/sound/vic20sound.c src/engine/platform/sound/vrcvi/vrcvi.cpp +src/engine/platform/sound/scc/scc.cpp + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -367,6 +369,7 @@ src/engine/platform/n163.cpp src/engine/platform/pet.cpp src/engine/platform/vic20.cpp src/engine/platform/vrc6.cpp +src/engine/platform/scc.cpp src/engine/platform/dummy.cpp ) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index f44ed0d5c..c04276ea7 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -54,6 +54,7 @@ #include "platform/vrc6.h" #include "platform/fds.h" #include "platform/mmc5.h" +#include "platform/scc.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -314,6 +315,14 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_MMC5: dispatch=new DivPlatformMMC5; break; + case DIV_SYSTEM_SCC: + dispatch=new DivPlatformSCC; + ((DivPlatformSCC*)dispatch)->setChipModel(false); + break; + case DIV_SYSTEM_SCC_PLUS: + dispatch=new DivPlatformSCC; + ((DivPlatformSCC*)dispatch)->setChipModel(true); + break; case DIV_SYSTEM_SOUND_UNIT: dispatch=new DivPlatformSoundUnit; break; diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp new file mode 100644 index 000000000..87a42afee --- /dev/null +++ b/src/engine/platform/scc.cpp @@ -0,0 +1,387 @@ +/** + * 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 "scc.h" +#include "../engine.h" +#include + +#define CHIP_DIVIDER 32 + +#define rWrite(a,v) {if(!skipRegisterWrites) {scc->scc_w(true,a,v); regPool[a]=v; if(dumpWrites) addWrite(a,v); }} + +const char* regCheatSheetSCC[]={ + "Ch1_Wave", "00", + "Ch2_Wave", "20", + "Ch3_Wave", "40", + "Ch4_5_Wave", "60", + "Ch1_FreqL", "80", + "Ch1_FreqH", "81", + "Ch2_FreqL", "82", + "Ch2_FreqH", "83", + "Ch3_FreqL", "84", + "Ch3_FreqH", "85", + "Ch4_FreqL", "86", + "Ch4_FreqH", "87", + "Ch5_FreqL", "88", + "Ch5_FreqH", "89", + "Ch1_Vol", "8a", + "Ch2_Vol", "8b", + "Ch3_Vol", "8c", + "Ch4_Vol", "8d", + "Ch5_Vol", "8e", + "Output", "8f", + "Test", "e0", + NULL +}; + +const char* regCheatSheetSCCPlus[]={ + "Ch1_Wave", "00", + "Ch2_Wave", "20", + "Ch3_Wave", "40", + "Ch4_Wave", "60", + "Ch5_Wave", "80", + "Ch1_FreqL", "a0", + "Ch1_FreqH", "a1", + "Ch2_FreqL", "a2", + "Ch2_FreqH", "a3", + "Ch3_FreqL", "a4", + "Ch3_FreqH", "a5", + "Ch4_FreqL", "a6", + "Ch4_FreqH", "a7", + "Ch5_FreqL", "a8", + "Ch5_FreqH", "a9", + "Ch1_Vol", "aa", + "Ch2_Vol", "ab", + "Ch3_Vol", "ac", + "Ch4_Vol", "ad", + "Ch5_Vol", "ae", + "Output", "af", + "Test", "c0", + NULL +}; + +const char** DivPlatformSCC::getRegisterSheet() { + return isPlus ? regCheatSheetSCCPlus : regCheatSheetSCC; +} + +const char* DivPlatformSCC::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + } + return NULL; +} + +void DivPlatformSCC::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; htick(); + } + short out=(short)scc->out()<<5; + bufL[h]=bufR[h]=out; + + for (int i=0; i<5; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=scc->chan_out(i)<<7; + } + } +} + +void DivPlatformSCC::updateWave(int ch) { + int dstCh=(!isPlus && ch>=4)?3:ch; + for (int i=0; i<32; i++) { + rWrite(dstCh*32+i,(unsigned char)chan[ch].ws.output[i]-128); + } +} + +void DivPlatformSCC::tick(bool sysTick) { + for (int i=0; i<5; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))/15; + rWrite(regBase+10+i,chan[i].outVol); + } + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); + } else { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.wave.had) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; + chan[i].ws.changeWave1(chan[i].wave); + } + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].active) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } + if (chan[i].freqChanged) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].pitch2; + if (chan[i].freq<1) chan[i].freq=1; + if (chan[i].freq>4096) chan[i].freq=4096; + rWrite(regBase+0+i*2,(chan[i].freq-1)&0xff); + rWrite(regBase+1+i*2,(chan[i].freq-1)>>8); + chan[i].freqChanged=false; + } + } +} + +int DivPlatformSCC::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SCC); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].macroInit(ins); + if (!isMuted[c.chan]) { + rWrite(regBase+15,regPool[regBase+15]|(1<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=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(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].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SCC)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformSCC::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (mute) { + rWrite(regBase+15,regPool[regBase+15]&~(1<reset(); + for (int i=0; i<5; i++) { + chan[i]=DivPlatformSCC::Channel(); + chan[i].std.setEngine(parent); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,255,false); + chan[i].vol=15; + chan[i].outVol=15; + rWrite(regBase+10+i,15); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } +} + +bool DivPlatformSCC::isStereo() { + return false; +} + +void DivPlatformSCC::notifyWaveChange(int wave) { + for (int i=0; i<5; i++) { + if (chan[i].wave==wave) { + chan[i].ws.changeWave1(chan[i].wave); + updateWave(i); + } + } +} + +void DivPlatformSCC::notifyInsDeletion(void* ins) { + for (int i=0; i<5; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformSCC::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformSCC::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +void DivPlatformSCC::setChipModel(bool isplus) { + isPlus=isplus; +} + +int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + writeOscBuf=0; + for (int i=0; i<5; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + chipClock=COLOR_NTSC; + rate=chipClock/16; + for (int i=0; i<5; i++) { + oscBuf[i]->rate=rate; + } + if (isPlus) { + scc=new k052539_scc_core; + regBase=0xa0; + } else { + scc=new k051649_scc_core; + regBase=0x80; + } + reset(); + return 5; +} + +void DivPlatformSCC::quit() { + for (int i=0; i<5; i++) { + delete oscBuf[i]; + } + if (scc!=NULL) { + delete scc; + } +} + +DivPlatformSCC::~DivPlatformSCC() { +} diff --git a/src/engine/platform/scc.h b/src/engine/platform/scc.h new file mode 100644 index 000000000..58059532f --- /dev/null +++ b/src/engine/platform/scc.h @@ -0,0 +1,91 @@ +/** + * 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 _SCC_H +#define _SCC_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" +#include "../waveSynth.h" +#include "sound/scc/scc.hpp" + +class DivPlatformSCC: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, pitch2, note, ins; + bool active, insChanged, freqChanged, inPorta; + signed char vol, outVol, wave; + signed char waveROM[32] = {0}; // 4 bit PROM per channel on bubble system + DivMacroInt std; + DivWaveSynth ws; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + note(0), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + inPorta(false), + vol(15), + outVol(15), + wave(-1) {} + }; + Channel chan[5]; + DivDispatchOscBuffer* oscBuf[5]; + bool isMuted[5]; + unsigned char writeOscBuf; + + scc_core* scc; + bool isPlus; + unsigned char regBase; + unsigned char regPool[225]; + void updateWave(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); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool isStereo(); + 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 setChipModel(bool isPlus); + void quit(); + ~DivPlatformSCC(); +}; + +#endif diff --git a/src/engine/platform/sound/scc/scc.cpp b/src/engine/platform/sound/scc/scc.cpp new file mode 100644 index 000000000..db74d3f0e --- /dev/null +++ b/src/engine/platform/sound/scc/scc.cpp @@ -0,0 +1,612 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Konami SCC emulation core + + Konami SCC means "Sound Creative Chip", it's actually MSX MegaROM/RAM Mapper with 5 channel Wavetable sound generator. + + It was first appeared at 1987, F-1 Spirit and Nemesis 2/Gradius 2 for MSX. then several MSX cartridges used that until 1990, Metal Gear 2: Solid Snake. + Even after MSX is discontinued, it was still used at some low-end arcade and amusement hardwares. + and some Third-party MSX utilities still support this due to its market shares. + + There's 2 SCC types: + + K051649 (or simply known as SCC) + This chip is used for MSX MegaROM Mapper, some arcade machines. + Channel 4 and 5 must be share waveform, other channels has its own waveforms. + + K052539 (also known as SCC+) + This chip is used for MSX MegaRAM Mapper (Konami Sound Cartridges for Snatcher/SD Snatcher). + All channels can be has its own waveforms, and also has backward compatibility mode with K051649. + + Based on: + https://www.msx.org/wiki/MegaROM_Mappers + https://www.msx.org/wiki/Konami_051649 + https://www.msx.org/wiki/Konami_052539 + http://bifi.msxnet.org/msxnet/tech/scc + http://bifi.msxnet.org/msxnet/tech/soundcartridge + + K051649 Register Layout + + -------------------------------------------------------------------- + + 4000-bfff MegaROM Mapper + + -------------------------------------------------------------------- + + Address Bit R/W Description + 7654 3210 + + 4000-5fff xxxx xxxx R Bank page 0 + c000-dfff mirror of 4000-5fff + + 6000-7fff xxxx xxxx R Bank page 1 + e000-ffff mirror of 6000-7fff + + 8000-9fff xxxx xxxx R Bank page 2 + 0000-1fff mirror of 8000-9fff + + a000-bfff xxxx xxxx R Bank page 3 + 2000-3fff mirror of a000-bfff + + -------------------------------------------------------------------- + + 5000-57ff, 7000-77ff, 9000-97ff, b000-b7ff Bank select + + -------------------------------------------------------------------- + + Address Bit R/W Description + 7654 3210 + + 5000 --xx xxxx W Bank select, Page 0 + 5001-57ff Mirror of 5000 + + 7000 --xx xxxx W Bank select, Page 1 + 7001-77ff Mirror of 7000 + + 9000 --xx xxxx W Bank select, Page 2 + --11 1111 W SCC Enable + 9001-97ff Mirror of 9000 + + b000 --xx xxxx W Bank select, Page 3 + b001-b7ff Mirror of b000 + + -------------------------------------------------------------------- + + 9800-9fff SCC register + + -------------------------------------------------------------------- + + 9800-987f Waveform + + Address Bit R/W Description + 7654 3210 + + 9800-981f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed) + 9820-983f xxxx xxxx R/W Channel 1 "" + 9840-985f xxxx xxxx R/W Channel 2 "" + 9860-987f xxxx xxxx R/W Channel 3/4 "" + + 9880-9889 Pitch + + 9880 xxxx xxxx W Channel 0 Pitch LSB + 9881 ---- xxxx W Channel 0 Pitch MSB + 9882 xxxx xxxx W Channel 1 Pitch LSB + 9883 ---- xxxx W Channel 1 Pitch MSB + 9884 xxxx xxxx W Channel 2 Pitch LSB + 9885 ---- xxxx W Channel 2 Pitch MSB + 9886 xxxx xxxx W Channel 3 Pitch LSB + 9887 ---- xxxx W Channel 3 Pitch MSB + 9888 xxxx xxxx W Channel 4 Pitch LSB + 9889 ---- xxxx W Channel 4 Pitch MSB + + 9888-988e Volume + + 988a ---- xxxx W Channel 0 Volume + 988b ---- xxxx W Channel 1 Volume + 988c ---- xxxx W Channel 2 Volume + 988d ---- xxxx W Channel 3 Volume + 988e ---- xxxx W Channel 4 Volume + + 988f ---x ---- W Channel 4 Output enable/disable flag + ---- x--- W Channel 3 Output enable/disable flag + ---- -x-- W Channel 2 Output enable/disable flag + ---- --x- W Channel 1 Output enable/disable flag + ---- ---x W Channel 0 Output enable/disable flag + + 9890-989f Mirror of 9880-988f + + 98a0-98bf xxxx xxxx R Channel 4 Waveform + + 98e0 x--- ---- W Waveform rotate flag for channel 4 + -x-- ---- W Waveform rotate flag for all channels + --x- ---- W Reset waveform position after pitch writes + ---- --x- W 8 bit frequency + ---- --0x W 4 bit frequency + + 98e1-98ff Mirror of 98e0 + + 9900-9fff Mirror of 9800-98ff + + -------------------------------------------------------------------- + + K052539 Register Layout + + -------------------------------------------------------------------- + + 4000-bfff MegaRAM Mapper + + -------------------------------------------------------------------- + + Address Bit R/W Description + 7654 3210 + + 4000-5fff xxxx xxxx R/W Bank page 0 + c000-dfff xxxx xxxx R/W "" + + 6000-7fff xxxx xxxx R/W Bank page 1 + e000-ffff xxxx xxxx R/W "" + + 8000-9fff xxxx xxxx R/W Bank page 2 + 0000-1fff xxxx xxxx R/W "" + + a000-bfff xxxx xxxx R/W Bank page 3 + 2000-3fff xxxx xxxx R/W "" + + -------------------------------------------------------------------- + + 5000-57ff, 7000-77ff, 9000-97ff, b000-b7ff Bank select + + -------------------------------------------------------------------- + + Address Bit R/W Description + 7654 3210 + + 5000 xxxx xxxx W Bank select, Page 0 + 5001-57ff Mirror of 5000 + + 7000 xxxx xxxx W Bank select, Page 1 + 7001-77ff Mirror of 7000 + + 9000 xxxx xxxx W Bank select, Page 2 + --11 1111 W SCC Enable (SCC Compatible mode) + 9001-97ff Mirror of 9000 + + b000 xxxx xxxx W Bank select, Page 3 + 1--- ---- W SCC+ Enable (SCC+ mode) + b001-b7ff Mirror of b000 + + -------------------------------------------------------------------- + + bffe-bfff Mapper configuration + + -------------------------------------------------------------------- + + Address Bit R/W Description + 7654 3210 + + bffe --x- ---- W SCC operation mode + --0- ---- W SCC Compatible mode + --1- ---- W SCC+ mode + ---x ---- W RAM write/Bank select toggle for all Bank pages + ---0 ---- W Bank select enable + ---1 ---- W RAM write enable + ---0 -x-- W RAM write/Bank select toggle for Bank page 2 + ---0 --x- W RAM write/Bank select toggle for Bank page 1 + ---0 ---x W RAM write/Bank select toggle for Bank page 0 + bfff Mirror of bffe + + -------------------------------------------------------------------- + + 9800-9fff SCC Compatible mode register + + -------------------------------------------------------------------- + + 9800-987f Waveform + + Address Bit R/W Description + 7654 3210 + + 9800-981f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed) + 9820-983f xxxx xxxx R/W Channel 1 "" + 9840-985f xxxx xxxx R/W Channel 2 "" + 9860-987f xxxx xxxx R/W Channel 3/4 "" + + 9880-9889 Pitch + + 9880 xxxx xxxx W Channel 0 Pitch LSB + 9881 ---- xxxx W Channel 0 Pitch MSB + 9882 xxxx xxxx W Channel 1 Pitch LSB + 9883 ---- xxxx W Channel 1 Pitch MSB + 9884 xxxx xxxx W Channel 2 Pitch LSB + 9885 ---- xxxx W Channel 2 Pitch MSB + 9886 xxxx xxxx W Channel 3 Pitch LSB + 9887 ---- xxxx W Channel 3 Pitch MSB + 9888 xxxx xxxx W Channel 4 Pitch LSB + 9889 ---- xxxx W Channel 4 Pitch MSB + + 9888-988e Volume + + 988a ---- xxxx W Channel 0 Volume + 988b ---- xxxx W Channel 1 Volume + 988c ---- xxxx W Channel 2 Volume + 988d ---- xxxx W Channel 3 Volume + 988e ---- xxxx W Channel 4 Volume + + 988f ---x ---- W Channel 4 Output enable/disable flag + ---- x--- W Channel 3 Output enable/disable flag + ---- -x-- W Channel 2 Output enable/disable flag + ---- --x- W Channel 1 Output enable/disable flag + ---- ---x W Channel 0 Output enable/disable flag + + 9890-989f Mirror of 9880-988f + + 98a0-98bf xxxx xxxx R Channel 4 Waveform + + 98c0 -x-- ---- W Waveform rotate flag for all channels + --x- ---- W Reset waveform position after pitch writes + ---- --x- W 8 bit frequency + ---- --0x W 4 bit frequency + + 98c1-98df Mirror of 98c0 + + 9900-9fff Mirror of 9800-98ff + + -------------------------------------------------------------------- + + b800-bfff SCC+ mode register + + -------------------------------------------------------------------- + + b800-b89f Waveform + + Address Bit R/W Description + 7654 3210 + + b800-b81f xxxx xxxx R/W Channel 0 Waveform (32 byte, 8 bit signed) + b820-b83f xxxx xxxx R/W Channel 1 "" + b840-b85f xxxx xxxx R/W Channel 2 "" + b860-b87f xxxx xxxx R/W Channel 3 "" + b880-b89f xxxx xxxx R/W Channel 3 "" + + b8a0-b8a9 Pitch + + b8a0 xxxx xxxx W Channel 0 Pitch LSB + b8a1 ---- xxxx W Channel 0 Pitch MSB + b8a2 xxxx xxxx W Channel 1 Pitch LSB + b8a3 ---- xxxx W Channel 1 Pitch MSB + b8a4 xxxx xxxx W Channel 2 Pitch LSB + b8a5 ---- xxxx W Channel 2 Pitch MSB + b8a6 xxxx xxxx W Channel 3 Pitch LSB + b8a7 ---- xxxx W Channel 3 Pitch MSB + b8a8 xxxx xxxx W Channel 4 Pitch LSB + b8a9 ---- xxxx W Channel 4 Pitch MSB + + b8a8-b8ae Volume + + b8aa ---- xxxx W Channel 0 Volume + b8ab ---- xxxx W Channel 1 Volume + b8ac ---- xxxx W Channel 2 Volume + b8ad ---- xxxx W Channel 3 Volume + b8ae ---- xxxx W Channel 4 Volume + + b8af ---x ---- W Channel 4 Output enable/disable flag + ---- x--- W Channel 3 Output enable/disable flag + ---- -x-- W Channel 2 Output enable/disable flag + ---- --x- W Channel 1 Output enable/disable flag + ---- ---x W Channel 0 Output enable/disable flag + + b8b0-b8bf Mirror of b8a0-b8af + + b8c0 -x-- ---- W Waveform rotate flag for all channels + --x- ---- W Reset waveform position after pitch writes + ---- --x- W 8 bit frequency + ---- --0x W 4 bit frequency + + b8c1-b8df Mirror of b8c0 + + b900-bfff Mirror of b800-b8ff + + -------------------------------------------------------------------- + + SCC Frequency calculation: + if 8 bit frequency then + Frequency = Input clock / ((bit 0 to 7 of Pitch input) + 1) + else if 4 bit frequency then + Frequency = Input clock / ((bit 8 to 11 of Pitch input) + 1) + else + Frequency = Input clock / (Pitch input + 1) + +*/ + +#include "scc.hpp" + +// shared SCC features +void scc_core::tick() +{ + m_out = 0; + for (auto & elem : m_voice) + { + elem.tick(); + m_out += elem.out; + } +} + +void scc_core::voice_t::tick() +{ + if (pitch >= 9) // or voice is halted + { + // update counter - Post decrement + u16 temp = counter; + if (m_host.m_test.freq_4bit) // 4 bit frequency mode + { + counter = (counter & ~0x0ff) | (bitfield(bitfield(counter, 0, 8) - 1, 0, 8) << 0); + counter = (counter & ~0xf00) | (bitfield(bitfield(counter, 8, 4) - 1, 0, 4) << 8); + } + else + counter = bitfield(counter - 1, 0, 12); + + // handle counter carry + bool carry = m_host.m_test.freq_8bit ? (bitfield(temp, 0, 8) == 0) : + (m_host.m_test.freq_4bit ? (bitfield(temp, 8, 4) == 0) : + (bitfield(temp, 0, 12) == 0)); + if (carry) + { + addr = bitfield(addr + 1, 0, 5); + counter = pitch; + } + } + // get output + if (enable) + out = (wave[addr] * volume) >> 4; // scale to 11 bit digital output + else + out = 0; +} + +void scc_core::reset() +{ + for (auto & elem : m_voice) + elem.reset(); + + m_test.reset(); + m_out = 0; + std::fill(std::begin(m_reg), std::end(m_reg), 0); +} + +void scc_core::voice_t::reset() +{ + std::fill(std::begin(wave), std::end(wave), 0); + enable = false; + pitch = 0; + volume = 0; + addr = 0; + counter = 0; + out = 0; +} + +// SCC accessors +u8 scc_core::wave_r(bool is_sccplus, u8 address) +{ + u8 ret = 0xff; + const u8 voice = bitfield(address, 5, 3); + if (voice > 4) + return ret; + + u8 wave_addr = bitfield(address, 0, 5); + + if (m_test.rotate) // rotate flag + wave_addr = bitfield(wave_addr + m_voice[voice].addr, 0, 5); + + if (!is_sccplus) + { + if (voice == 3) // rotate voice 3~4 flag + { + if (m_test.rotate4 || m_test.rotate) // rotate flag + wave_addr = bitfield(bitfield(address, 0, 5) + m_voice[3 + m_test.rotate].addr, 0, 5); + } + } + ret = m_voice[voice].wave[wave_addr]; + + return ret; +} + +void scc_core::wave_w(bool is_sccplus, u8 address, u8 data) +{ + if (m_test.rotate) // write protected + return; + + const u8 voice = bitfield(address, 5, 3); + if (voice > 4) + return; + + const u8 wave_addr = bitfield(address, 0, 5); + + if (!is_sccplus) + { + if (((voice >= 3) && m_test.rotate4) || (voice >= 4)) // Ignore if write protected, or voice 4 + return; + if (voice >= 3) // voice 3, 4 shares waveform + { + m_voice[3].wave[wave_addr] = data; + m_voice[4].wave[wave_addr] = data; + } + else + m_voice[voice].wave[wave_addr] = data; + } + else + m_voice[voice].wave[wave_addr] = data; +} + +void scc_core::freq_vol_enable_w(u8 address, u8 data) +{ + const u8 voice_freq = bitfield(address, 1, 3); + const u8 voice_reg = bitfield(address, 0, 4); + // *0-*f Pitch, Volume, Enable + switch (voice_reg) + { + case 0x0: // 0x*0 Voice 0 Pitch LSB + case 0x2: // 0x*2 Voice 1 Pitch LSB + case 0x4: // 0x*4 Voice 2 Pitch LSB + case 0x6: // 0x*6 Voice 3 Pitch LSB + case 0x8: // 0x*8 Voice 4 Pitch LSB + if (m_test.resetpos) // Reset address + m_voice[voice_freq].addr = 0; + m_voice[voice_freq].pitch = (m_voice[voice_freq].pitch & ~0x0ff) | data; + break; + case 0x1: // 0x*1 Voice 0 Pitch MSB + case 0x3: // 0x*3 Voice 1 Pitch MSB + case 0x5: // 0x*5 Voice 2 Pitch MSB + case 0x7: // 0x*7 Voice 3 Pitch MSB + case 0x9: // 0x*9 Voice 4 Pitch MSB + if (m_test.resetpos) // Reset address + m_voice[voice_freq].addr = 0; + m_voice[voice_freq].pitch = (m_voice[voice_freq].pitch & ~0xf00) | (u16(bitfield(data, 0, 4)) << 8); + break; + case 0xa: // 0x*a Voice 0 Volume + case 0xb: // 0x*b Voice 1 Volume + case 0xc: // 0x*c Voice 2 Volume + case 0xd: // 0x*d Voice 3 Volume + case 0xe: // 0x*e Voice 4 Volume + m_voice[voice_reg - 0xa].volume = bitfield(data, 0, 4); + break; + case 0xf: // 0x*f Enable/Disable flag + m_voice[0].enable = bitfield(data, 0); + m_voice[1].enable = bitfield(data, 1); + m_voice[2].enable = bitfield(data, 2); + m_voice[3].enable = bitfield(data, 3); + m_voice[4].enable = bitfield(data, 4); + break; + } +} + +void k051649_scc_core::scc_w(bool is_sccplus, u8 address, u8 data) +{ + const u8 voice = bitfield(address, 5, 3); + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3/4 Waveform + wave_w(false, address, data); + break; + case 0b100: // 0x80-0x9f Pitch, Volume, Enable + freq_vol_enable_w(address, data); + break; + case 0b111: // 0xe0-0xff Test register + m_test.freq_4bit = bitfield(data, 0); + m_test.freq_8bit = bitfield(data, 1); + m_test.resetpos = bitfield(data, 5); + m_test.rotate = bitfield(data, 6); + m_test.rotate4 = bitfield(data, 7); + break; + } + m_reg[address] = data; +} + +void k052539_scc_core::scc_w(bool is_sccplus, u8 address, u8 data) +{ + const u8 voice = bitfield(address, 5, 3); + if (is_sccplus) + { + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3 Waveform + case 0b100: // 0x80-0x9f Voice 4 Waveform + wave_w(true, address, data); + break; + case 0b101: // 0xa0-0xbf Pitch, Volume, Enable + freq_vol_enable_w(address, data); + break; + case 0b110: // 0xc0-0xdf Test register + m_test.freq_4bit = bitfield(data, 0); + m_test.freq_8bit = bitfield(data, 1); + m_test.resetpos = bitfield(data, 5); + m_test.rotate = bitfield(data, 6); + break; + default: + break; + } + } + else + { + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3/4 Waveform + wave_w(false, address, data); + break; + case 0b100: // 0x80-0x9f Pitch, Volume, Enable + freq_vol_enable_w(address, data); + break; + case 0b110: // 0xc0-0xdf Test register + m_test.freq_4bit = bitfield(data, 0); + m_test.freq_8bit = bitfield(data, 1); + m_test.resetpos = bitfield(data, 5); + m_test.rotate = bitfield(data, 6); + break; + default: + break; + } + } + m_reg[address] = data; +} + +u8 k051649_scc_core::scc_r(bool is_sccplus, u8 address) +{ + const u8 voice = bitfield(address, 5, 3); + const u8 wave = bitfield(address, 0, 5); + u8 ret = 0xff; + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3 Waveform + case 0b101: // 0xa0-0xbf Voice 4 Waveform + ret = wave_r(false, (std::min(4, voice) << 5) | wave); + break; + } + return ret; +} + +u8 k052539_scc_core::scc_r(bool is_sccplus, u8 address) +{ + const u8 voice = bitfield(address, 5, 3); + const u8 wave = bitfield(address, 0, 5); + u8 ret = 0xff; + if (is_sccplus) + { + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3 Waveform + case 0b100: // 0x80-0x9f Voice 4 Waveform + ret = wave_r(true, address); + break; + } + } + else + { + switch (voice) + { + case 0b000: // 0x00-0x1f Voice 0 Waveform + case 0b001: // 0x20-0x3f Voice 1 Waveform + case 0b010: // 0x40-0x5f Voice 2 Waveform + case 0b011: // 0x60-0x7f Voice 3 Waveform + case 0b101: // 0xa0-0xbf Voice 4 Waveform + ret = wave_r(false, (std::min(4, voice) << 5) | wave); + break; + } + } + return ret; +} diff --git a/src/engine/platform/sound/scc/scc.hpp b/src/engine/platform/sound/scc/scc.hpp new file mode 100644 index 000000000..a4580bad4 --- /dev/null +++ b/src/engine/platform/sound/scc/scc.hpp @@ -0,0 +1,136 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Konami SCC emulation core + + See scc.cpp for more info. +*/ + +#include +#include + +#ifndef _VGSOUND_EMU_SCC_HPP +#define _VGSOUND_EMU_SCC_HPP + +#pragma once + +namespace scc +{ + typedef unsigned char u8; + typedef signed char s8; + typedef unsigned short u16; + typedef signed short s16; + typedef unsigned int u32; + typedef signed int s32; + + // get bitfield, bitfield(input, position, len) + template T bitfield(T in, u8 pos, u8 len = 1) + { + return (in >> pos) & (len ? (T(1 << len) - 1) : 1); + } +} + +using namespace scc; +// shared for SCCs +class scc_core +{ +public: + // constructor + scc_core() + : m_voice{*this,*this,*this,*this,*this} + {}; + + // accessors + virtual u8 scc_r(bool is_sccplus, u8 address) = 0; + virtual void scc_w(bool is_sccplus, u8 address, u8 data) = 0; + + // internal state + virtual void reset(); + void tick(); + + // getters + s32 out() { return m_out; } // output to DA0...DA10 pin + s32 chan_out(u8 ch) { return m_voice[ch].out; } + u8 reg(u8 address) { return m_reg[address]; } + +protected: + // voice structs + struct voice_t + { + // constructor + voice_t(scc_core &host) : m_host(host) {}; + + // internal state + void reset(); + void tick(); + + // registers + scc_core &m_host; + s8 wave[32] = {0}; // internal waveform + bool enable = false; // output enable flag + u16 pitch = 0; // pitch + u8 volume = 0; // volume + u8 addr = 0; // waveform pointer + u16 counter = 0; // frequency counter + s32 out = 0; // current output + }; + voice_t m_voice[5]; // 5 voices + + // accessor + u8 wave_r(bool is_sccplus, u8 address); + void wave_w(bool is_sccplus, u8 address, u8 data); + void freq_vol_enable_w(u8 address, u8 data); + + struct test_t + { + // constructor + test_t() + : freq_4bit(0) + , freq_8bit(0) + , resetpos(0) + , rotate(0) + , rotate4(0) + { }; + + void reset() + { + freq_4bit = 0; + freq_8bit = 0; + resetpos = 0; + rotate = 0; + rotate4 = 0; + } + + u8 freq_4bit : 1; // 4 bit frequency + u8 freq_8bit : 1; // 8 bit frequency + u8 resetpos : 1; // reset counter after pitch writes + u8 rotate : 1; // rotate and write protect waveform for all channels + u8 rotate4 : 1; // same as above but for channel 4 only + }; + + test_t m_test; // test register + s32 m_out = 0; // output to DA0...10 + + u8 m_reg[256] = {0}; // register pool +}; + +// SCC core +class k051649_scc_core : public scc_core +{ +public: + // accessors + virtual u8 scc_r(bool is_sccplus, u8 address) override; + virtual void scc_w(bool is_sccplus, u8 address, u8 data) override; +}; + +class k052539_scc_core : public k051649_scc_core +{ +public: + // accessors + virtual u8 scc_r(bool is_sccplus, u8 address) override; + virtual void scc_w(bool is_sccplus, u8 address, u8 data) override; +}; + +#endif diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 6760acb6a..792f16d5f 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -859,6 +859,8 @@ const int availableSystems[]={ DIV_SYSTEM_VRC6, DIV_SYSTEM_FDS, DIV_SYSTEM_MMC5, + DIV_SYSTEM_SCC, + DIV_SYSTEM_SCC_PLUS, 0 // don't remove this last one! }; diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 3dd24e0af..28a57f861 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -425,6 +425,20 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + SCC", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_SCC, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + SCC+", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_SCC_PLUS, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "ZX Spectrum (48K)", { DIV_SYSTEM_AY8910, 64, 0, 2, From 78ce2360bf1dbd1aab06bdbd11da487445bbd330 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 10 May 2022 11:32:07 +0700 Subject: [PATCH 02/68] SCC has no config flags currently --- src/gui/sysConf.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 8005ee0d3..edde54f8a 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -367,6 +367,8 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YMU759: case DIV_SYSTEM_PET: + case DIV_SYSTEM_SCC: + case DIV_SYSTEM_SCC_PLUS: ImGui::Text("nothing to configure"); break; default: From 28c0751659a97f41cdeee04a1f73d2c4d5ff9c11 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 10 May 2022 12:49:48 +0700 Subject: [PATCH 03/68] Remove SCC from TODO.md --- TODO.md | 1 - 1 file changed, 1 deletion(-) diff --git a/TODO.md b/TODO.md index c11d47558..fc526b29e 100644 --- a/TODO.md +++ b/TODO.md @@ -9,7 +9,6 @@ - OPNA system - ZX beeper system - Y8950 system -- SCC/SCC+ system - maybe YMU759 ADPCM channel - ADPCM chips - Game Boy envelope macro/sequence From 5e14177e4e07d2d4246dfbc6c5d5f3d2fe68ce76 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 10 May 2022 02:22:08 -0500 Subject: [PATCH 04/68] YM2610(B): use f-num/block baseFreq calculation also allow for different octave boundary f-nums depending on chip clock --- src/engine/engine.cpp | 12 +++- src/engine/fileOps.cpp | 4 +- src/engine/platform/genesis.cpp | 10 +-- src/engine/platform/genesisext.cpp | 10 +-- src/engine/platform/ym2610.cpp | 99 +++++++++++++----------------- src/engine/platform/ym2610.h | 5 +- src/engine/platform/ym2610b.cpp | 99 +++++++++++++----------------- src/engine/platform/ym2610b.h | 5 +- src/engine/platform/ym2610bext.cpp | 56 ++++++++++++----- src/engine/platform/ym2610bext.h | 4 +- src/engine/platform/ym2610ext.cpp | 60 +++++++++++++----- src/engine/platform/ym2610ext.h | 4 +- 12 files changed, 198 insertions(+), 170 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 94c138a94..10f61c17a 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -993,19 +993,24 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri } unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) { + double tuning=song.tuning; + if (tuning<400.0) tuning=400.0; + if (tuning>500.0) tuning=500.0; int bf=calcBaseFreq(clock,divider,note,false); + int boundaryBottom=tuning*pow(2.0,0.25)*(divider/clock); + int boundaryTop=2.0*tuning*pow(2.0,0.25)*(divider/clock); int block=note/12; if (block<0) block=0; if (block>7) block=7; bf>>=block; if (bf<0) bf=0; // octave boundaries - while (bf>0 && bf<644 && block>0) { + while (bf>0 && bf0) { bf<<=1; block--; } - if (bf>1288) { - while (block<7) { + if (bf>boundaryTop) { + while (block<7 && bf>boundaryTop) { bf>>=1; block++; } @@ -1013,6 +1018,7 @@ unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, in bf=(1<0x0c) { diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 0f665cba1..4b4771a3e 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -725,6 +725,8 @@ int DivPlatformGenesis::dispatch(DivCommand c) { } break; } + int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); + int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); int destFreq=NOTE_FNUM_BLOCK(c.value2,11); int newFreq; bool return2=false; @@ -747,13 +749,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) { // check for octave boundary // what the heck! if (!chan[c.chan].portaPause) { - if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) { - chan[c.chan].portaPauseFreq=(644)|((newFreq+0x800)&0xf800); + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { + chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); chan[c.chan].portaPause=true; break; } - if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) { - chan[c.chan].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800); + if ((newFreq&0x7ff)0) { + chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); chan[c.chan].portaPause=true; break; } diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index ae4d74cd9..3c9304a04 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -127,6 +127,8 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { + int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); + int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); int destFreq=NOTE_FNUM_BLOCK(c.value2,11); int newFreq; bool return2=false; @@ -148,18 +150,18 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { } // what the heck! if (!opChan[ch].portaPause) { - if ((newFreq&0x7ff)>1288 && (newFreq&0xf800)<0x3800) { + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { if (parent->song.fbPortaPause) { - opChan[ch].portaPauseFreq=(644)|((newFreq+0x800)&0xf800); + opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); opChan[ch].portaPause=true; break; } else { newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800); } } - if ((newFreq&0x7ff)<644 && (newFreq&0xf800)>0) { + if ((newFreq&0x7ff)0) { if (parent->song.fbPortaPause) { - opChan[ch].portaPauseFreq=newFreq=(1287)|((newFreq-0x800)&0xf800); + opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); opChan[ch].portaPause=true; break; } else { diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 54bbbd213..46a66fe07 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -456,7 +456,7 @@ double DivPlatformYM2610::NOTE_OPNB(int ch, int note) { return NOTE_PERIODIC(note); } // FM - return NOTE_FREQUENCY(note); + return NOTE_FNUM_BLOCK(note,11); } double DivPlatformYM2610::NOTE_ADPCMB(int note) { @@ -559,15 +559,15 @@ void DivPlatformYM2610::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); } } chan[i].freqChanged=true; } else { if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); chan[i].freqChanged=true; } } @@ -742,11 +742,20 @@ void DivPlatformYM2610::tick(bool sysTick) { for (int i=0; i<4; i++) { if (i==1 && extMode) continue; if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); - if (chan[i].freq>262143) chan[i].freq=262143; - int freqt=toFreq(chan[i].freq)+chan[i].pitch2; - immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); - immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); + int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2); + int block=(chan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; + } + chan[i].freq=(block<<11)|fNum; + if (chan[i].freq>0x3fff) chan[i].freq=0x3fff; + immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); + immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } if (chan[i].keyOn) { @@ -756,47 +765,6 @@ void DivPlatformYM2610::tick(bool sysTick) { } } -int DivPlatformYM2610::octave(int freq) { - if (freq>=622.0f*128) { - return 128; - } else if (freq>=622.0f*64) { - return 64; - } else if (freq>=622.0f*32) { - return 32; - } else if (freq>=622.0f*16) { - return 16; - } else if (freq>=622.0f*8) { - return 8; - } else if (freq>=622.0f*4) { - return 4; - } else if (freq>=622.0f*2) { - return 2; - } else { - return 1; - } - return 1; -} - -int DivPlatformYM2610::toFreq(int freq) { - if (freq>=622.0f*128) { - return 0x3800|((freq>>7)&0x7ff); - } else if (freq>=622.0f*64) { - return 0x3000|((freq>>6)&0x7ff); - } else if (freq>=622.0f*32) { - return 0x2800|((freq>>5)&0x7ff); - } else if (freq>=622.0f*16) { - return 0x2000|((freq>>4)&0x7ff); - } else if (freq>=622.0f*8) { - return 0x1800|((freq>>3)&0x7ff); - } else if (freq>=622.0f*4) { - return 0x1000|((freq>>2)&0x7ff); - } else if (freq>=622.0f*2) { - return 0x800|((freq>>1)&0x7ff); - } else { - return freq&0x7ff; - } -} - int DivPlatformYM2610::dispatch(DivCommand c) { if (c.chan>3 && c.chan<7) { c.chan-=4; @@ -928,7 +896,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].insChanged=false; if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); chan[c.chan].portaPause=false; chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; @@ -1048,33 +1016,48 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } break; } - - int destFreq=NOTE_FREQUENCY(c.value2); + int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); + int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); int newFreq; bool return2=false; + if (chan[c.chan].portaPause) { + chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq; + } if (destFreq>chan[c.chan].baseFreq) { - newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq+c.value; if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq-c.value; if (newFreq<=destFreq) { newFreq=destFreq; return2=true; } } + // check for octave boundary + // what the heck! if (!chan[c.chan].portaPause) { - if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { + chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + if ((newFreq&0x7ff)0) { + chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); chan[c.chan].portaPause=true; break; } } - chan[c.chan].baseFreq=newFreq; chan[c.chan].portaPause=false; chan[c.chan].freqChanged=true; - if (return2) return 2; + chan[c.chan].baseFreq=newFreq; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } break; } case DIV_CMD_SAMPLE_BANK: diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 45ecb335a..92f4ca077 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -61,7 +61,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, note, ins; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; unsigned char psgMode, autoEnvNum, autoEnvDen; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; @@ -80,6 +80,7 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { baseFreq(0), pitch(0), pitch2(0), + portaPauseFreq(0), note(0), ins(-1), psgMode(1), @@ -125,8 +126,6 @@ class DivPlatformYM2610: public DivPlatformYM2610Base { short oldWrites[512]; short pendingWrites[512]; - 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 b36cc87f5..03d8f3a26 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -436,7 +436,7 @@ double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { return NOTE_PERIODIC(note); } // FM - return NOTE_FREQUENCY(note); + return NOTE_FNUM_BLOCK(note,11); } double DivPlatformYM2610B::NOTE_ADPCMB(int note) { @@ -538,15 +538,15 @@ void DivPlatformYM2610B::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { if (chan[i].std.arp.mode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); } } chan[i].freqChanged=true; } else { if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); chan[i].freqChanged=true; } } @@ -720,11 +720,20 @@ void DivPlatformYM2610B::tick(bool sysTick) { for (int i=0; i<6; i++) { if (i==2 && extMode) continue; if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); - if (chan[i].freq>262143) chan[i].freq=262143; - int freqt=toFreq(chan[i].freq)+chan[i].pitch2; - immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); - immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); + int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2); + int block=(chan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; + } + chan[i].freq=(block<<11)|fNum; + if (chan[i].freq>0x3fff) chan[i].freq=0x3fff; + immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); + immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); chan[i].freqChanged=false; } if (chan[i].keyOn) { @@ -734,47 +743,6 @@ void DivPlatformYM2610B::tick(bool sysTick) { } } -int DivPlatformYM2610B::octave(int freq) { - if (freq>=622.0f*128) { - return 128; - } else if (freq>=622.0f*64) { - return 64; - } else if (freq>=622.0f*32) { - return 32; - } else if (freq>=622.0f*16) { - return 16; - } else if (freq>=622.0f*8) { - return 8; - } else if (freq>=622.0f*4) { - return 4; - } else if (freq>=622.0f*2) { - return 2; - } else { - return 1; - } - return 1; -} - -int DivPlatformYM2610B::toFreq(int freq) { - if (freq>=622.0f*128) { - return 0x3800|((freq>>7)&0x7ff); - } else if (freq>=622.0f*64) { - return 0x3000|((freq>>6)&0x7ff); - } else if (freq>=622.0f*32) { - return 0x2800|((freq>>5)&0x7ff); - } else if (freq>=622.0f*16) { - return 0x2000|((freq>>4)&0x7ff); - } else if (freq>=622.0f*8) { - return 0x1800|((freq>>3)&0x7ff); - } else if (freq>=622.0f*4) { - return 0x1000|((freq>>2)&0x7ff); - } else if (freq>=622.0f*2) { - return 0x800|((freq>>1)&0x7ff); - } else { - return freq&0x7ff; - } -} - int DivPlatformYM2610B::dispatch(DivCommand c) { if (c.chan>5 && c.chan<9) { c.chan-=6; @@ -906,7 +874,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].insChanged=false; if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); chan[c.chan].portaPause=false; chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; @@ -1026,33 +994,48 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } break; } - - int destFreq=NOTE_FREQUENCY(c.value2); + int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); + int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); int newFreq; bool return2=false; + if (chan[c.chan].portaPause) { + chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq; + } if (destFreq>chan[c.chan].baseFreq) { - newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq+c.value; if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq-c.value; if (newFreq<=destFreq) { newFreq=destFreq; return2=true; } } + // check for octave boundary + // what the heck! if (!chan[c.chan].portaPause) { - if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { + chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + if ((newFreq&0x7ff)0) { + chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); chan[c.chan].portaPause=true; break; } } - chan[c.chan].baseFreq=newFreq; chan[c.chan].portaPause=false; chan[c.chan].freqChanged=true; - if (return2) return 2; + chan[c.chan].baseFreq=newFreq; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } break; } case DIV_CMD_SAMPLE_BANK: diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index d1af3069a..3a034028a 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -35,7 +35,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, note, ins; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; unsigned char psgMode, autoEnvNum, autoEnvDen; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; @@ -54,6 +54,7 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { baseFreq(0), pitch(0), pitch2(0), + portaPauseFreq(0), note(0), ins(-1), psgMode(1), @@ -98,8 +99,6 @@ class DivPlatformYM2610B: public DivPlatformYM2610Base { short oldWrites[512]; short pendingWrites[512]; - 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/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 03b91fccc..ac991ffb8 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -120,31 +120,51 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_FREQUENCY(c.value2); + int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); + int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); int newFreq; bool return2=false; + if (opChan[ch].portaPause) { + opChan[ch].baseFreq=opChan[ch].portaPauseFreq; + } if (destFreq>opChan[ch].baseFreq) { - newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq); + newFreq=opChan[ch].baseFreq+c.value; if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq); + newFreq=opChan[ch].baseFreq-c.value; if (newFreq<=destFreq) { newFreq=destFreq; return2=true; } } + // what the heck! if (!opChan[ch].portaPause) { - if (octave(opChan[ch].baseFreq)!=octave(newFreq)) { - opChan[ch].portaPause=true; - break; + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { + if (parent->song.fbPortaPause) { + opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); + opChan[ch].portaPause=true; + break; + } else { + newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800); + } + } + if ((newFreq&0x7ff)0) { + if (parent->song.fbPortaPause) { + opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); + opChan[ch].portaPause=true; + break; + } else { + newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800); + } } } - opChan[ch].baseFreq=newFreq; opChan[ch].portaPause=false; opChan[ch].freqChanged=true; + opChan[ch].baseFreq=newFreq; if (return2) return 2; break; } @@ -364,14 +384,20 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { unsigned char writeMask=2; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,octave(opChan[i].baseFreq),opChan[i].pitch2); - if (opChan[i].freq>262143) opChan[i].freq=262143; - int freqt=toFreq(opChan[i].freq); - opChan[i].freqH=freqt>>8; - opChan[i].freqL=freqt&0xff; - immWrite(opChanOffsH[i],opChan[i].freqH); - immWrite(opChanOffsL[i],opChan[i].freqL); - opChan[i].freqChanged=false; + int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2); + int block=(opChan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; + } + opChan[i].freq=(block<<11)|fNum; + if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff; + immWrite(opChanOffsH[i],opChan[i].freq>>8); + immWrite(opChanOffsL[i],opChan[i].freq&0xff); } writeMask|=opChan[i].active<<(4+i); if (opChan[i].keyOn) { diff --git a/src/engine/platform/ym2610bext.h b/src/engine/platform/ym2610bext.h index b416b5c70..4e3f7c5ff 100644 --- a/src/engine/platform/ym2610bext.h +++ b/src/engine/platform/ym2610bext.h @@ -25,12 +25,12 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B { struct OpChannel { DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, ins; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; int vol; unsigned char pan; - OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} + OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 1ffbd74fe..98273bd9a 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -63,7 +63,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { opChan[ch].insChanged=false; if (c.value!=DIV_NOTE_NULL) { - opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11); opChan[ch].portaPause=false; opChan[ch].freqChanged=true; } @@ -120,36 +120,56 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_FREQUENCY(c.value2); + int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); + int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); int newFreq; bool return2=false; + if (opChan[ch].portaPause) { + opChan[ch].baseFreq=opChan[ch].portaPauseFreq; + } if (destFreq>opChan[ch].baseFreq) { - newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq); + newFreq=opChan[ch].baseFreq+c.value; if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq); + newFreq=opChan[ch].baseFreq-c.value; if (newFreq<=destFreq) { newFreq=destFreq; return2=true; } } + // what the heck! if (!opChan[ch].portaPause) { - if (octave(opChan[ch].baseFreq)!=octave(newFreq)) { - opChan[ch].portaPause=true; - break; + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { + if (parent->song.fbPortaPause) { + opChan[ch].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); + opChan[ch].portaPause=true; + break; + } else { + newFreq=(newFreq>>1)|((newFreq+0x800)&0xf800); + } + } + if ((newFreq&0x7ff)0) { + if (parent->song.fbPortaPause) { + opChan[ch].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); + opChan[ch].portaPause=true; + break; + } else { + newFreq=(newFreq<<1)|((newFreq-0x800)&0xf800); + } } } - opChan[ch].baseFreq=newFreq; opChan[ch].portaPause=false; opChan[ch].freqChanged=true; + opChan[ch].baseFreq=newFreq; if (return2) return 2; break; } case DIV_CMD_LEGATO: { - opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11); opChan[ch].freqChanged=true; break; } @@ -364,14 +384,20 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { unsigned char writeMask=2; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,octave(opChan[i].baseFreq),opChan[i].pitch2); - if (opChan[i].freq>262143) opChan[i].freq=262143; - int freqt=toFreq(opChan[i].freq); - opChan[i].freqH=freqt>>8; - opChan[i].freqL=freqt&0xff; - immWrite(opChanOffsH[i],opChan[i].freqH); - immWrite(opChanOffsL[i],opChan[i].freqL); - opChan[i].freqChanged=false; + int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2); + int block=(opChan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; + } + opChan[i].freq=(block<<11)|fNum; + if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff; + immWrite(opChanOffsH[i],opChan[i].freq>>8); + immWrite(opChanOffsL[i],opChan[i].freq&0xff); } writeMask|=opChan[i].active<<(4+i); if (opChan[i].keyOn) { diff --git a/src/engine/platform/ym2610ext.h b/src/engine/platform/ym2610ext.h index 10be6638d..287bf08b3 100644 --- a/src/engine/platform/ym2610ext.h +++ b/src/engine/platform/ym2610ext.h @@ -25,12 +25,12 @@ class DivPlatformYM2610Ext: public DivPlatformYM2610 { struct OpChannel { DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, ins; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins; signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; int vol; unsigned char pan; - OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} + OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), pitch2(0), portaPauseFreq(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} }; OpChannel opChan[4]; bool isOpMuted[4]; From 8784fe56647ffa76c491faf71224f6870d3e5875 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 10 May 2022 03:51:18 -0500 Subject: [PATCH 05/68] dev94 - add a full linear pitch mode, part 1 --- papers/format.md | 6 +++- src/engine/engine.cpp | 21 ++++++++--- src/engine/engine.h | 6 ++-- src/engine/fileOps.cpp | 16 ++++++--- src/engine/platform/amiga.cpp | 2 +- src/engine/platform/ay.cpp | 2 +- src/engine/platform/ay8930.cpp | 2 +- src/engine/platform/bubsyswsg.cpp | 2 +- src/engine/platform/c64.cpp | 2 +- src/engine/platform/dummy.cpp | 2 +- src/engine/platform/fds.cpp | 2 +- src/engine/platform/gb.cpp | 2 +- src/engine/platform/lynx.cpp | 2 +- src/engine/platform/mmc5.cpp | 2 +- src/engine/platform/n163.cpp | 2 +- src/engine/platform/nes.cpp | 2 +- src/engine/platform/opl.cpp | 2 +- src/engine/platform/opll.cpp | 2 +- src/engine/platform/pce.cpp | 2 +- src/engine/platform/pcspkr.cpp | 2 +- src/engine/platform/pet.cpp | 2 +- src/engine/platform/qsound.cpp | 58 +++++++++---------------------- src/engine/platform/saa.cpp | 2 +- src/engine/platform/sms.cpp | 4 +-- src/engine/platform/su.cpp | 2 +- src/engine/platform/swan.cpp | 2 +- src/engine/platform/vera.cpp | 3 +- src/engine/platform/vic20.cpp | 2 +- src/engine/platform/vrc6.cpp | 4 +-- src/engine/platform/x1_010.cpp | 3 +- src/engine/playback.cpp | 4 +-- src/engine/song.h | 10 ++++-- src/gui/compatFlags.cpp | 35 ++++++++++++++++--- 33 files changed, 123 insertions(+), 89 deletions(-) diff --git a/papers/format.md b/papers/format.md index 7ab678002..e6d35e832 100644 --- a/papers/format.md +++ b/papers/format.md @@ -232,6 +232,9 @@ size | description 4f | A-4 tuning 1 | limit slides (>=36) or reserved 1 | linear pitch (>=36) or reserved + | - 0: non-linaer + | - 1: only pitch change (04xy/E5xx) linear + | - 2: full linear (>=94) 1 | loop modality (>=36) or reserved 1 | proper noise layout (>=42) or reserved 1 | wave duty is volume (>=42) or reserved @@ -286,7 +289,8 @@ size | description 1 | weird f-num/block-based chip pitch slides (>=85) or reserved 1 | SN duty macro always resets phase (>=86) or reserved 1 | pitch macro is linear (>=90) or reserved - 19 | reserved + 1 | pitch slide speed in full linear pitch mode (>=94) or reserved + 18 | reserved ``` # instrument diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 10f61c17a..83bc56b86 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -986,6 +986,9 @@ int DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) }*/ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) { + if (song.linearPitch==2) { // full linear + return (note<<7); + } double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0); return period? (clock/base)/divider: @@ -993,6 +996,9 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri } unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, int note, int bits) { + if (song.linearPitch==2) { // full linear + return (note<<7); + } double tuning=song.tuning; if (tuning<400.0) tuning=400.0; if (tuning>500.0) tuning=500.0; @@ -1018,12 +1024,19 @@ unsigned short DivEngine::calcBaseFreqFNumBlock(double clock, double divider, in bf=(1<dispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff; chan[i].volume=chan[i].volMax; - if (!song.linearPitch) chan[i].vibratoFine=4; + if (song.linearPitch==0) chan[i].vibratoFine=4; } extValue=0; extValuePresent=0; diff --git a/src/engine/engine.h b/src/engine/engine.h index 4b5614a6f..9ac26d890 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -45,8 +45,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev93" -#define DIV_ENGINE_VERSION 93 +#define DIV_VERSION "dev94" +#define DIV_ENGINE_VERSION 94 // for imports #define DIV_VERSION_MOD 0xff01 @@ -484,7 +484,7 @@ class DivEngine { unsigned short calcBaseFreqFNumBlock(double clock, double divider, int note, int bits); // calculate frequency/period - int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0); + int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0, double clock=1.0, double divider=1.0); // convert panning formats int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 4eb73e7eb..e0f207948 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -139,7 +139,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { // compatibility flags ds.limitSlides=true; - ds.linearPitch=true; + ds.linearPitch=1; ds.loopModality=0; ds.properNoiseLayout=false; ds.waveDutyIsVol=false; @@ -950,7 +950,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<37) { // compat flags not stored back then ds.limitSlides=true; - ds.linearPitch=true; + ds.linearPitch=1; ds.loopModality=0; } if (ds.version<43) { @@ -1392,7 +1392,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<19; i++) { + if (ds.version>=94) { + ds.pitchSlideSpeed=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<18; i++) { reader.readC(); } } @@ -1649,7 +1654,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_MOD; - ds.linearPitch=false; + ds.linearPitch=0; ds.noSlidesOnFirstTick=true; ds.rowResetsArpPos=true; ds.ignoreJumpAtEnd=false; @@ -2702,7 +2707,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(song.fbPortaPause); w->writeC(song.snDutyReset); w->writeC(song.pitchMacroIsLinear); - for (int i=0; i<19; i++) { + w->writeC(song.pitchSlideSpeed); + for (int i=0; i<18; i++) { w->writeC(0); } diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index e78c3a77b..df742d352 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -220,7 +220,7 @@ void DivPlatformAmiga::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOn) { diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 37108452e..dbc5aa82b 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -251,7 +251,7 @@ void DivPlatformAY8910::tick(bool sysTick) { if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].keyOn) { //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 3fec732b5..6fb340968 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -280,7 +280,7 @@ void DivPlatformAY8930::tick(bool sysTick) { immWrite(0x1a,ayNoiseOr); } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq>65535) chan[i].freq=65535; if (chan[i].keyOn) { if (chan[i].insChanged) { diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index b12a92ed4..52fb4a50b 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -137,7 +137,7 @@ void DivPlatformBubSysWSG::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SCC); - chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].pitch2; + chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>4095) chan[i].freq=4095; k005289->load(i,chan[i].freq); diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index c0efdd6f2..17b8124d9 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -217,7 +217,7 @@ void DivPlatformC64::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].freq>0xffff) chan[i].freq=0xffff; if (chan[i].keyOn) { rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay)); diff --git a/src/engine/platform/dummy.cpp b/src/engine/platform/dummy.cpp index 34d614eb3..48c33f059 100644 --- a/src/engine/platform/dummy.cpp +++ b/src/engine/platform/dummy.cpp @@ -61,7 +61,7 @@ void DivPlatformDummy::tick(bool sysTick) { if (chan[i].freqChanged) { chan[i].freqChanged=false; - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,0,chipClock,CHIP_FREQBASE); } } } diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index f3908148d..5698f0041 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -220,7 +220,7 @@ void DivPlatformFDS::tick(bool sysTick) { } } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOn) { diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 1fc2b278e..963d2fde0 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -237,7 +237,7 @@ void DivPlatformGB::tick(bool sysTick) { if (ntPos>255) ntPos=255; chan[i].freq=noiseTable[ntPos]; } else { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq>2047) chan[i].freq=2047; if (chan[i].freq<0) chan[i].freq=0; } diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 095710612..dd5a2be01 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -201,7 +201,7 @@ void DivPlatformLynx::tick(bool sysTick) { WRITE_OTHER(i, ((chan[i].lfsr&0xf00)>>4)); chan[i].lfsr=-1; } - chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].std.duty.had) { chan[i].duty=chan[i].std.duty.val; WRITE_FEEDBACK(i, chan[i].duty.feedback); diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 8446643b6..9775e6397 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -146,7 +146,7 @@ void DivPlatformMMC5::tick(bool sysTick) { } } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1; if (chan[i].freq>2047) chan[i].freq=2047; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOn) { diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index ef368203b..9318b4f8b 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -357,7 +357,7 @@ void DivPlatformN163::tick(bool sysTick) { chan[i].waveUpdated=false; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff; if (chan[i].keyOn) { diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 4b301839d..ef3b51f06 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -308,7 +308,7 @@ void DivPlatformNES::tick(bool sysTick) { if (ntPos>252) ntPos=252; chan[i].freq=(parent->song.properNoiseLayout)?(15-(chan[i].baseFreq&15)):(noiseTable[ntPos]); } else { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1; if (chan[i].freq>2047) chan[i].freq=2047; if (chan[i].freq<0) chan[i].freq=0; } diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index ff31ad403..b95b42edd 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -511,7 +511,7 @@ void DivPlatformOPL::tick(bool sysTick) { bool updateDrums=false; for (int i=0; icalcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].freq>131071) chan[i].freq=131071; int freqt=toFreq(chan[i].freq)+chan[i].pitch2; chan[i].freqH=freqt>>8; diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index b4f0916a8..89b413179 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -300,7 +300,7 @@ void DivPlatformOPLL::tick(bool sysTick) { for (int i=0; i<11; i++) { if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].freq>262143) chan[i].freq=262143; int freqt=toFreq(chan[i].freq)+chan[i].pitch2; chan[i].freqL=freqt&0xff; diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index c28b81db7..9838f987f 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -227,7 +227,7 @@ void DivPlatformPCE::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].furnaceDac) { double off=1.0; if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 434ab10b4..8ecf6dae9 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -223,7 +223,7 @@ void DivPlatformPCSpeaker::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>65535) chan[i].freq=65535; if (chan[i].keyOn) { diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp index 8ddee9e86..f2ee6addd 100644 --- a/src/engine/platform/pet.cpp +++ b/src/engine/platform/pet.cpp @@ -118,7 +118,7 @@ void DivPlatformPET::tick(bool sysTick) { chan.freqChanged=true; } if (chan.freqChanged || chan.keyOn || chan.keyOff) { - chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2); + chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER); if (chan.freq>257) chan.freq=257; if (chan.freq<2) chan.freq=2; rWrite(8,chan.freq-2); diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 6e89e5b61..e9b4e8a48 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -24,7 +24,7 @@ #include #define CHIP_DIVIDER (1248*2) -#define QS_NOTE_FREQUENCY(x) parent->calcBaseFreq(440,0x1000,(x)-3,false) +#define QS_NOTE_FREQUENCY(x) parent->calcBaseFreq(440,4096,(x)-3,false) #define rWrite(a,v) {if(!skipRegisterWrites) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v); }} #define immWrite(a,v) {qsound_write_data(&chip,a,v); if(dumpWrites) addWrite(a,v);} @@ -296,14 +296,8 @@ void DivPlatformQSound::tick(bool sysTick) { uint16_t qsound_addr = 0; uint16_t qsound_loop = 0; uint16_t qsound_end = 0; - double off=1.0; if (chan[i].sample>=0 && chan[i].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[i].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=(double)s->centerRate/24038.0/16.0; - } qsound_bank = 0x8000 | (s->offQSound >> 16); qsound_addr = s->offQSound & 0xffff; @@ -322,15 +316,15 @@ void DivPlatformQSound::tick(bool sysTick) { if (chan[i].std.arp.had) { if (!chan[i].inPorta) { if (chan[i].std.arp.mode) { - chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].std.arp.val); + chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].std.arp.val); } else { - chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); + chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { if (chan[i].std.arp.mode && chan[i].std.arp.finished) { - chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note); + chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note); chan[i].freqChanged=true; } } @@ -354,7 +348,16 @@ void DivPlatformQSound::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2); + double off=1.0; + if (chan[i].sample>=0 && chan[i].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=(double)s->centerRate/24038.0/16.0; + } + } + chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,440.0,4096.0); if (chan[i].freq>0xffff) chan[i].freq=0xffff; if (chan[i].keyOn) { rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank); @@ -388,17 +391,8 @@ int DivPlatformQSound::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); chan[c.chan].sample=ins->amiga.initSample; - double off=1.0; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[c.chan].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=(double)s->centerRate/24038.0/16.0; - } - } if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value); + chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value); } if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { chan[c.chan].sample=-1; @@ -467,16 +461,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; case DIV_CMD_NOTE_PORTA: { - double off=1.0; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[c.chan].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=(double)s->centerRate/24038.0/16.0; - } - } - int destFreq=off*QS_NOTE_FREQUENCY(c.value2); + int destFreq=QS_NOTE_FREQUENCY(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -499,16 +484,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: { - double off=1.0; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[c.chan].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=(double)s->centerRate/24038.0/16.0; - } - } - chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0))); + chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index 3ecf0a3ed..b662b1e35 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -200,7 +200,7 @@ void DivPlatformSAA1099::tick(bool sysTick) { rWrite(0x18+(i/3),saaEnv[i/3]); } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq>65535) chan[i].freq=65535; if (chan[i].freq>=32768) { chan[i].freqH=7; diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index f1c661634..be2039cf8 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -121,7 +121,7 @@ void DivPlatformSMS::tick(bool sysTick) { } for (int i=0; i<3; i++) { if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,64); if (chan[i].freq>1023) chan[i].freq=1023; if (chan[i].freq<8) chan[i].freq=1; //if (chan[i].actualNote>0x5d) chan[i].freq=0x01; @@ -136,7 +136,7 @@ void DivPlatformSMS::tick(bool sysTick) { } } if (chan[3].freqChanged || updateSNMode) { - chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2); + chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,isRealSN?60:64); if (chan[3].freq>1023) chan[3].freq=1023; if (chan[3].actualNote>0x5d) chan[3].freq=0x01; if (snNoiseMode&2) { // take period from channel 3 diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index e80153f07..4630f90e6 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -183,7 +183,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].pcm) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); DivSample* sample=parent->getSample(ins->amiga.initSample); diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 97aa7b9ef..b089394a7 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -203,7 +203,7 @@ void DivPlatformSwan::tick(bool sysTick) { } } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (i==1 && pcm && furnaceDac) { double off=1.0; if (dacSample>=0 && dacSamplesong.sampleLen) { diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 4dcc8f6c5..0c19ea541 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -147,6 +147,7 @@ void DivPlatformVERA::reset() { chan[16].pan=3; } +// TODO: linear pitch stuff int DivPlatformVERA::calcNoteFreq(int ch, int note) { if (ch<16) { return parent->calcBaseFreq(chipClock,2097152,note,false); @@ -208,7 +209,7 @@ void DivPlatformVERA::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2,chipClock,2097152); if (chan[i].freq>65535) chan[i].freq=65535; rWrite(i,0,chan[i].freq&0xff); rWrite(i,1,(chan[i].freq>>8)&0xff); diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index 33250bfc6..1c8c1fe0a 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -132,7 +132,7 @@ void DivPlatformVIC20::tick(bool sysTick) { chan[i].freqChanged=true; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (i<3) { chan[i].freq>>=(2-i); } else { diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 835acf4b8..4e8117942 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -197,9 +197,9 @@ void DivPlatformVRC6::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (i==2) { // sawtooth - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,14)-1; } else { // pulse - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,16)-1; if (chan[i].furnaceDac) { double off=1.0; if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 03cd4f6be..35226f503 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -260,6 +260,7 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l } } +// TODO: linear pitch stuff double DivPlatformX1_010::NoteX1_010(int ch, int note) { if (chan[ch].pcm) { // PCM note double off=1.0; @@ -487,7 +488,7 @@ void DivPlatformX1_010::tick(bool sysTick) { 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,2,chan[i].pitch2); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].pcm) { if (chan[i].freq<1) chan[i].freq=1; if (chan[i].freq>255) chan[i].freq=255; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 224838e84..feec7d961 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -631,7 +631,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].scheduledSlideReset=false; chan[i].inPorta=false; if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); - dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote)); + dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote)); chan[i].portaNote=-1; chan[i].portaSpeed=-1; chan[i].inPorta=false; @@ -954,7 +954,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } if (!song.noSlidesOnFirstTick || !firstTick) { if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { - if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { + if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed*(song.linearPitch==2?song.pitchSlideSpeed:1),chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { chan[i].portaSpeed=0; chan[i].oldNote=chan[i].note; chan[i].note=chan[i].portaNote; diff --git a/src/engine/song.h b/src/engine/song.h index 7b823a8b6..489b744bb 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -301,7 +301,12 @@ struct DivSong { // compatibility flags bool limitSlides; - bool linearPitch; + // linear pitch + // 0: not linear + // 1: only pitch changes (04xy/E5xx) linear + // 2: full linear + unsigned char linearPitch; + unsigned char pitchSlideSpeed; // loop behavior // 0: reset on loop // 1: fake reset on loop @@ -412,7 +417,8 @@ struct DivSong { masterVol(1.0f), tuning(440.0f), limitSlides(false), - linearPitch(true), + linearPitch(1), + pitchSlideSpeed(4), loopModality(0), properNoiseLayout(false), waveDutyIsVol(false), diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index 52f465027..461048d62 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -18,6 +18,8 @@ */ #include "gui.h" +#include "imgui.h" +#include "intConst.h" void FurnaceGUI::drawCompatFlags() { if (nextWindow==GUI_WINDOW_COMPAT_FLAGS) { @@ -32,10 +34,6 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves."); } - ImGui::Checkbox("Linear pitch control",&e->song.linearPitch); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work in tonality space\nnon-linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work on frequency/period space"); - } ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("use a proper noise channel note mapping (0-15) instead of a rather unusual compatible one.\nunlocks all noise frequencies on PC Engine."); @@ -126,6 +124,35 @@ void FurnaceGUI::drawCompatFlags() { ImGui::SetTooltip("when enabled, the pitch macro of an instrument is in linear space."); } + ImGui::Text("Pitch linearity:"); + if (ImGui::RadioButton("None",e->song.linearPitch==0)) { + e->song.linearPitch=0; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("like ProTracker/FamiTracker"); + } + if (ImGui::RadioButton("Partial (only 04xy/E5xx)",e->song.linearPitch==1)) { + e->song.linearPitch=1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("like DefleMask"); + } + if (ImGui::RadioButton("Full",e->song.linearPitch==2)) { + e->song.linearPitch=2; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("like Impulse Tracker"); + } + + if (e->song.linearPitch==2) { + ImGui::SameLine(); + ImGui::SetNextItemWidth(120.0f*dpiScale); + if (ImGui::InputScalar("Pitch slide speed multiplier",ImGuiDataType_U8,&e->song.pitchSlideSpeed,&_ONE,&_ONE)) { + if (e->song.pitchSlideSpeed<1) e->song.pitchSlideSpeed=1; + if (e->song.pitchSlideSpeed>64) e->song.pitchSlideSpeed=64; + } + } + ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { e->song.loopModality=0; From 960a8e36165970444a8e1d86aa38add4b4862b9c Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 10 May 2022 17:36:09 +0700 Subject: [PATCH 06/68] Fix GCC errors --- src/engine/platform/scc.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index 87a42afee..f23240e1c 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -379,7 +379,11 @@ void DivPlatformSCC::quit() { delete oscBuf[i]; } if (scc!=NULL) { - delete scc; + if (isPlus) { + delete (k052539_scc_core*)scc; + } else { + delete (k051649_scc_core*)scc; + } } } From 00414c6504da3a61760d0600381d62cf8f643a96 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Wed, 11 May 2022 00:29:17 +0700 Subject: [PATCH 07/68] Fix GCC errors 2 --- src/engine/platform/scc.cpp | 6 +----- src/engine/platform/sound/scc/scc.hpp | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index f23240e1c..87a42afee 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -379,11 +379,7 @@ void DivPlatformSCC::quit() { delete oscBuf[i]; } if (scc!=NULL) { - if (isPlus) { - delete (k052539_scc_core*)scc; - } else { - delete (k051649_scc_core*)scc; - } + delete scc; } } diff --git a/src/engine/platform/sound/scc/scc.hpp b/src/engine/platform/sound/scc/scc.hpp index a4580bad4..e696c403d 100644 --- a/src/engine/platform/sound/scc/scc.hpp +++ b/src/engine/platform/sound/scc/scc.hpp @@ -41,6 +41,7 @@ public: scc_core() : m_voice{*this,*this,*this,*this,*this} {}; + virtual ~scc_core(){}; // accessors virtual u8 scc_r(bool is_sccplus, u8 address) = 0; From c79e5e8081111516c457545f0647e987d5ceea26 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 10 May 2022 16:19:49 -0500 Subject: [PATCH 08/68] GUI: window movement only by clicking on title bar on by default now --- 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 c10aea384..64aa78c36 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -970,7 +970,7 @@ class FurnaceGUI { powerSave(1), absorbInsInput(0), eventDelay(0), - moveWindowTitle(0), + moveWindowTitle(1), hiddenSystems(0), horizontalDataView(0), noMultiSystem(0), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 52d14777a..1c22755a8 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1845,7 +1845,7 @@ void FurnaceGUI::syncSettings() { settings.powerSave=e->getConfInt("powerSave",POWER_SAVE_DEFAULT); settings.absorbInsInput=e->getConfInt("absorbInsInput",0); settings.eventDelay=e->getConfInt("eventDelay",0); - settings.moveWindowTitle=e->getConfInt("moveWindowTitle",0); + settings.moveWindowTitle=e->getConfInt("moveWindowTitle",1); settings.hiddenSystems=e->getConfInt("hiddenSystems",0); settings.horizontalDataView=e->getConfInt("horizontalDataView",0); settings.noMultiSystem=e->getConfInt("noMultiSystem",0); From aad5d818f26aee716cdfcf9ff98b0c6aa3cd717e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 10 May 2022 16:22:40 -0500 Subject: [PATCH 09/68] YM2612: early full linear pitch experiment u n t e s t e d --- src/engine/engine.cpp | 71 +++++++++++++++++++-------------- src/engine/engine.h | 2 +- src/engine/platform/genesis.cpp | 5 ++- 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 83bc56b86..7465c8853 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -995,46 +995,55 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri base*(divider/clock); } +#define CONVERT_FNUM_BLOCK(bf,bits,note) \ + double tuning=song.tuning; \ + if (tuning<400.0) tuning=400.0; \ + if (tuning>500.0) tuning=500.0; \ + int boundaryBottom=tuning*pow(2.0,0.25)*(divider/clock); \ + int boundaryTop=2.0*tuning*pow(2.0,0.25)*(divider/clock); \ + int block=(note)/12; \ + if (block<0) block=0; \ + if (block>7) block=7; \ + bf>>=block; \ + if (bf<0) bf=0; \ + /* octave boundaries */ \ + while (bf>0 && bf0) { \ + bf<<=1; \ + block--; \ + } \ + if (bf>boundaryTop) { \ + while (block<7 && bf>boundaryTop) { \ + bf>>=1; \ + block++; \ + } \ + if (bf>((1<500.0) tuning=500.0; int bf=calcBaseFreq(clock,divider,note,false); - int boundaryBottom=tuning*pow(2.0,0.25)*(divider/clock); - int boundaryTop=2.0*tuning*pow(2.0,0.25)*(divider/clock); - int block=note/12; - if (block<0) block=0; - if (block>7) block=7; - bf>>=block; - if (bf<0) bf=0; - // octave boundaries - while (bf>0 && bf0) { - bf<<=1; - block--; - } - if (bf>boundaryTop) { - while (block<7 && bf>boundaryTop) { - bf>>=1; - block++; - } - if (bf>((1<0) { + CONVERT_FNUM_BLOCK(bf,blockBits,nbase>>7) + } else { + return bf; + } } if (song.linearPitch==1) { // global pitch multiplier diff --git a/src/engine/engine.h b/src/engine/engine.h index 9ac26d890..4fcc5f1b7 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -484,7 +484,7 @@ class DivEngine { unsigned short calcBaseFreqFNumBlock(double clock, double divider, int note, int bits); // calculate frequency/period - int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0, double clock=1.0, double divider=1.0); + int calcFreq(int base, int pitch, bool period=false, int octave=0, int pitch2=0, double clock=1.0, double divider=1.0, int blockBits=0); // convert panning formats int convertPanSplitToLinear(unsigned int val, unsigned char bits, int range); diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 4b4771a3e..2136efeb6 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -471,7 +471,7 @@ void DivPlatformGenesis::tick(bool sysTick) { for (int i=0; i<6; i++) { if (i==2 && extMode) continue; if (chan[i].freqChanged) { - int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2); + int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); int block=(chan[i].baseFreq&0xf800)>>11; if (fNum<0) fNum=0; if (fNum>2047) { @@ -495,7 +495,8 @@ void DivPlatformGenesis::tick(bool sysTick) { off=(double)s->centerRate/8363.0; } } - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4)+chan[i].pitch2; + // TODO: linear + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,1,1)+chan[i].pitch2; dacRate=chan[i].freq*off; if (dacRate<1) dacRate=1; if (dumpWrites) addWrite(0xffff0001,dacRate); From 814804ba105380435ba35c5e41fb369147de8ea6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 10 May 2022 16:33:30 -0500 Subject: [PATCH 10/68] re-enable warnings are errors on MSVC --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 13ef95c74..293c4cd0f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -187,8 +187,6 @@ jobs: export USE_WAE=ON export CMAKE_EXTRA_ARGS=() if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then - # 1. Go to hell - export USE_WAE=OFF CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}') # Force static linking From 0cc2c06bbf7c901a412c3d09b03c26871695760f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 10 May 2022 19:40:35 -0500 Subject: [PATCH 11/68] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1eac106d2..caa4714cc 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ Available options: | Name | Default | Description | | :--: | :-----: | ----------- | -| `BUILD_GUI` | `ON` if not building for Android, otherwise `OFF` | Build the tracker (disable to build only a headless player) | +| `BUILD_GUI` | `ON` | Build the tracker (disable to build only a headless player) | | `WITH_JACK` | `ON` if system-installed JACK detected, otherwise `OFF` | Whether to build with JACK support. Auto-detects if JACK is available | | `SYSTEM_FMT` | `OFF` | Use a system-installed version of fmt instead of the vendored one | | `SYSTEM_LIBSNDFILE` | `OFF` | Use a system-installed version of libsndfile instead of the vendored one | From f5dbc37902c12493a7ee7dc7a26579f9026f5c62 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 10 May 2022 23:17:40 -0500 Subject: [PATCH 12/68] YM2612: implement full linear pitch (part 2) --- src/engine/engine.cpp | 2 +- src/engine/engine.h | 2 +- src/engine/platform/genesis.cpp | 48 +++++++++++++++++++++++------- src/engine/platform/genesisext.cpp | 45 ++++++++++++++++++++++------ 4 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 7465c8853..2ea91ecd0 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1023,7 +1023,7 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri /* logV("f-num: %d block: %d",bf,block); */ \ return bf|(block<calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); - int block=(chan[i].baseFreq&0xf800)>>11; - if (fNum<0) fNum=0; - if (fNum>2047) { - while (block<7) { - fNum>>=1; - block++; + if (parent->song.linearPitch==2) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); + } else { + int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); + int block=(chan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; } - if (fNum>2047) fNum=2047; + chan[i].freq=(block<<11)|fNum; } - chan[i].freq=(block<<11)|fNum; if (chan[i].freq>0x3fff) chan[i].freq=0x3fff; immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); @@ -495,8 +499,7 @@ void DivPlatformGenesis::tick(bool sysTick) { off=(double)s->centerRate/8363.0; } } - // TODO: linear - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,1,1)+chan[i].pitch2; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,1,1); dacRate=chan[i].freq*off; if (dacRate<1) dacRate=1; if (dumpWrites) addWrite(0xffff0001,dacRate); @@ -703,6 +706,29 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { + if (parent->song.linearPitch==2) { + int destFreq=NOTE_FREQUENCY(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; + } if (c.chan==5 && chan[c.chan].furnaceDac && dacMode) { int destFreq=parent->calcBaseFreq(1,1,c.value2,false); bool return2=false; diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 3c9304a04..f0c35eccf 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -127,6 +127,29 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { + if (parent->song.linearPitch==2) { + int destFreq=NOTE_FREQUENCY(c.value2); + bool return2=false; + if (destFreq>opChan[ch].baseFreq) { + opChan[ch].baseFreq+=c.value; + if (opChan[ch].baseFreq>=destFreq) { + opChan[ch].baseFreq=destFreq; + return2=true; + } + } else { + opChan[ch].baseFreq-=c.value; + if (opChan[ch].baseFreq<=destFreq) { + opChan[ch].baseFreq=destFreq; + return2=true; + } + } + opChan[ch].freqChanged=true; + if (return2) { + //opChan[ch].inPorta=false; + return 2; + } + break; + } int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); int destFreq=NOTE_FNUM_BLOCK(c.value2,11); @@ -437,17 +460,21 @@ void DivPlatformGenesisExt::tick(bool sysTick) { unsigned char writeMask=2; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2); - int block=(opChan[i].baseFreq&0xf800)>>11; - if (fNum<0) fNum=0; - if (fNum>2047) { - while (block<7) { - fNum>>=1; - block++; + if (parent->song.linearPitch==2) { + opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11); + } else { + int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2); + int block=(opChan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; } - if (fNum>2047) fNum=2047; + opChan[i].freq=(block<<11)|fNum; } - opChan[i].freq=(block<<11)|fNum; if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff; immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); From a7e6323db97913db6d43019797586175c0d77ce0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 10 May 2022 23:42:24 -0500 Subject: [PATCH 13/68] add a full linear pitch mode, part 3 round frequency/period --- src/engine/engine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 2ea91ecd0..fa68cd60f 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1037,8 +1037,8 @@ int DivEngine::calcFreq(int base, int pitch, bool period, int octave, int pitch2 int nbase=base+pitch+pitch2; double fbase=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(nbase+384)/(128.0*12.0)); int bf=period? - (clock/fbase)/divider: - fbase*(divider/clock); + round((clock/fbase)/divider): + round(fbase*(divider/clock)); if (blockBits>0) { CONVERT_FNUM_BLOCK(bf,blockBits,nbase>>7) } else { From f4652e658236e83ceb5aab3bc47311c1d2d98f6c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 00:04:36 -0500 Subject: [PATCH 14/68] YM2610(B): implement full linear pitch (part 4) --- src/engine/platform/ym2610.cpp | 35 ++++++++++++++--------- src/engine/platform/ym2610b.cpp | 35 ++++++++++++++--------- src/engine/platform/ym2610bext.cpp | 45 ++++++++++++++++++++++++------ src/engine/platform/ym2610ext.cpp | 45 ++++++++++++++++++++++++------ 4 files changed, 116 insertions(+), 44 deletions(-) diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 46a66fe07..397f6a8f1 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -461,8 +461,8 @@ double DivPlatformYM2610::NOTE_OPNB(int ch, int note) { double DivPlatformYM2610::NOTE_ADPCMB(int note) { 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); + double off=65535.0*(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0; + return parent->calcBaseFreq((double)chipClock/144,off,note,false); } return 0; } @@ -726,7 +726,12 @@ void DivPlatformYM2610::tick(bool sysTick) { } } if (chan[13].freqChanged) { - chan[13].freq=parent->calcFreq(chan[13].baseFreq,chan[13].pitch,false,4); + if (chan[13].sample>=0 && chan[13].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0; + chan[13].freq=parent->calcFreq(chan[13].baseFreq,chan[13].pitch,false,4,chan[13].pitch2,(double)chipClock/144,off); + } else { + chan[13].freq=0; + } immWrite(0x19,chan[13].freq&0xff); immWrite(0x1a,(chan[13].freq>>8)&0xff); chan[13].freqChanged=false; @@ -742,17 +747,21 @@ void DivPlatformYM2610::tick(bool sysTick) { for (int i=0; i<4; i++) { if (i==1 && extMode) continue; if (chan[i].freqChanged) { - int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2); - int block=(chan[i].baseFreq&0xf800)>>11; - if (fNum<0) fNum=0; - if (fNum>2047) { - while (block<7) { - fNum>>=1; - block++; + if (parent->song.linearPitch==2) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); + } else { + int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); + int block=(chan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; } - if (fNum>2047) fNum=2047; + chan[i].freq=(block<<11)|fNum; } - chan[i].freq=(block<<11)|fNum; if (chan[i].freq>0x3fff) chan[i].freq=0x3fff; immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); @@ -993,7 +1002,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (c.chan>3) { // PSG, ADPCM-B + if (c.chan>3 || parent->song.linearPitch==2) { // PSG, ADPCM-B int destFreq=NOTE_OPNB(c.chan,c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 03d8f3a26..cd40fe139 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -441,8 +441,8 @@ double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { double DivPlatformYM2610B::NOTE_ADPCMB(int note) { 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); + double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + return parent->calcBaseFreq((double)chipClock/144,off,note,false); } return 0; } @@ -704,7 +704,12 @@ void DivPlatformYM2610B::tick(bool sysTick) { } } if (chan[15].freqChanged) { - chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4); + if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4,chan[15].pitch2,(double)chipClock/144,off); + } else { + chan[15].freq=0; + } immWrite(0x19,chan[15].freq&0xff); immWrite(0x1a,(chan[15].freq>>8)&0xff); chan[15].freqChanged=false; @@ -720,17 +725,21 @@ void DivPlatformYM2610B::tick(bool sysTick) { for (int i=0; i<6; i++) { if (i==2 && extMode) continue; if (chan[i].freqChanged) { - int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2); - int block=(chan[i].baseFreq&0xf800)>>11; - if (fNum<0) fNum=0; - if (fNum>2047) { - while (block<7) { - fNum>>=1; - block++; + if (parent->song.linearPitch==2) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); + } else { + int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2); + int block=(chan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; } - if (fNum>2047) fNum=2047; + chan[i].freq=(block<<11)|fNum; } - chan[i].freq=(block<<11)|fNum; if (chan[i].freq>0x3fff) chan[i].freq=0x3fff; immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); @@ -971,7 +980,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (c.chan>5) { // PSG, ADPCM-B + if (c.chan>5 || parent->song.linearPitch==2) { // PSG, ADPCM-B int destFreq=NOTE_OPNB(c.chan,c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index ac991ffb8..440dce6a0 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -120,6 +120,29 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { + if (parent->song.linearPitch==2) { + int destFreq=NOTE_FREQUENCY(c.value2); + bool return2=false; + if (destFreq>opChan[ch].baseFreq) { + opChan[ch].baseFreq+=c.value; + if (opChan[ch].baseFreq>=destFreq) { + opChan[ch].baseFreq=destFreq; + return2=true; + } + } else { + opChan[ch].baseFreq-=c.value; + if (opChan[ch].baseFreq<=destFreq) { + opChan[ch].baseFreq=destFreq; + return2=true; + } + } + opChan[ch].freqChanged=true; + if (return2) { + //opChan[ch].inPorta=false; + return 2; + } + break; + } int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); int destFreq=NOTE_FNUM_BLOCK(c.value2,11); @@ -384,17 +407,21 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { unsigned char writeMask=2; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2); - int block=(opChan[i].baseFreq&0xf800)>>11; - if (fNum<0) fNum=0; - if (fNum>2047) { - while (block<7) { - fNum>>=1; - block++; + if (parent->song.linearPitch==2) { + opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11); + } else { + int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2); + int block=(opChan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; } - if (fNum>2047) fNum=2047; + opChan[i].freq=(block<<11)|fNum; } - opChan[i].freq=(block<<11)|fNum; if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff; immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 98273bd9a..b12fd3de4 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -120,6 +120,29 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { + if (parent->song.linearPitch==2) { + int destFreq=NOTE_FREQUENCY(c.value2); + bool return2=false; + if (destFreq>opChan[ch].baseFreq) { + opChan[ch].baseFreq+=c.value; + if (opChan[ch].baseFreq>=destFreq) { + opChan[ch].baseFreq=destFreq; + return2=true; + } + } else { + opChan[ch].baseFreq-=c.value; + if (opChan[ch].baseFreq<=destFreq) { + opChan[ch].baseFreq=destFreq; + return2=true; + } + } + opChan[ch].freqChanged=true; + if (return2) { + //opChan[ch].inPorta=false; + return 2; + } + break; + } int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); int destFreq=NOTE_FNUM_BLOCK(c.value2,11); @@ -384,17 +407,21 @@ void DivPlatformYM2610Ext::tick(bool sysTick) { unsigned char writeMask=2; if (extMode) for (int i=0; i<4; i++) { if (opChan[i].freqChanged) { - int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2); - int block=(opChan[i].baseFreq&0xf800)>>11; - if (fNum<0) fNum=0; - if (fNum>2047) { - while (block<7) { - fNum>>=1; - block++; + if (parent->song.linearPitch==2) { + opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch,false,4,opChan[i].pitch2,chipClock,CHIP_FREQBASE,11); + } else { + int fNum=parent->calcFreq(opChan[i].baseFreq&0x7ff,opChan[i].pitch,false,4,opChan[i].pitch2); + int block=(opChan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; } - if (fNum>2047) fNum=2047; + opChan[i].freq=(block<<11)|fNum; } - opChan[i].freq=(block<<11)|fNum; if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff; immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsL[i],opChan[i].freq&0xff); From 90ac64f1d43243a9248b33d439ae16239381b7f4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 00:11:24 -0500 Subject: [PATCH 15/68] VERA: implement full linear pitch (part 5) --- src/engine/platform/vera.cpp | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 0c19ea541..58aa55b92 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -147,21 +147,20 @@ void DivPlatformVERA::reset() { chan[16].pan=3; } -// TODO: linear pitch stuff int DivPlatformVERA::calcNoteFreq(int ch, int note) { if (ch<16) { return parent->calcBaseFreq(chipClock,2097152,note,false); } else { - double off=1.0; + double off=65536.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; + off=65536.0; } else { - off=s->centerRate/8363.0; + off=65536.0*(s->centerRate/8363.0); } } - return (int)(off*parent->calcBaseFreq(chipClock,65536,note,false)); + return (int)(parent->calcBaseFreq(chipClock,off,note,false)); } } @@ -238,7 +237,16 @@ void DivPlatformVERA::tick(bool sysTick) { } } if (chan[16].freqChanged) { - chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8,chan[16].pitch2); + double off=65536.0; + if (chan[16].pcm.sample>=0 && chan[16].pcm.samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[16].pcm.sample); + if (s->centerRate<1) { + off=65536.0; + } else { + off=65536.0*(s->centerRate/8363.0); + } + } + chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8,chan[16].pitch2,chipClock,off); if (chan[16].freq>128) chan[16].freq=128; rWritePCMRate(chan[16].freq&0xff); chan[16].freqChanged=false; @@ -249,8 +257,8 @@ int DivPlatformVERA::dispatch(DivCommand c) { int tmp; switch (c.cmd) { case DIV_CMD_NOTE_ON: - if(c.chan<16) { - rWriteLo(c.chan,2,chan[c.chan].vol) + if (c.chan<16) { + rWriteLo(c.chan,2,chan[c.chan].vol); } else { chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.initSample; if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) { @@ -298,11 +306,15 @@ int DivPlatformVERA::dispatch(DivCommand c) { if (c.chan<16) { tmp=c.value&0x3f; chan[c.chan].vol=tmp; - rWriteLo(c.chan,2,tmp); + if (chan[c.chan].active) { + rWriteLo(c.chan,2,tmp); + } } else { tmp=c.value&0x0f; chan[c.chan].vol=tmp; - rWritePCMVol(tmp); + if (chan[c.chan].active) { + rWritePCMVol(tmp); + } } break; case DIV_CMD_GET_VOLUME: From ea118db49984e16c640ec6d6cbdc31a7762b0d09 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 00:18:46 -0500 Subject: [PATCH 16/68] X1-010: implement linear pitch (part 6) --- src/engine/platform/x1_010.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 35226f503..f1711243c 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -260,20 +260,19 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l } } -// TODO: linear pitch stuff double DivPlatformX1_010::NoteX1_010(int ch, int note) { if (chan[ch].pcm) { // PCM note - double off=1.0; + double off=8192.0; int sample=chan[ch].sample; if (sample>=0 && samplesong.sampleLen) { DivSample* s=parent->getSample(sample); if (s->centerRate<1) { - off=1.0; + off=8192.0; } else { - off=s->centerRate/8363.0; + off=8192.0*(s->centerRate/8363.0); } } - return off*parent->calcBaseFreq(chipClock,8192,note,false); + return parent->calcBaseFreq(chipClock,off,note,false); } // Wavetable note return NOTE_FREQUENCY(note); @@ -488,7 +487,19 @@ void DivPlatformX1_010::tick(bool sysTick) { 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,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); + double off=8192.0; + if (chan[i].pcm) { + int sample=chan[i].sample; + if (sample>=0 && samplesong.sampleLen) { + DivSample* s=parent->getSample(sample); + if (s->centerRate<1) { + off=8192.0; + } else { + off=8192.0*(s->centerRate/8363.0); + } + } + } + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,chan[i].pcm?off:CHIP_FREQBASE); if (chan[i].pcm) { if (chan[i].freq<1) chan[i].freq=1; if (chan[i].freq>255) chan[i].freq=255; From 1c6e9c2ccaea50c1eb17a3ab03f575894e872abf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 00:26:28 -0500 Subject: [PATCH 17/68] add a full linear pitch mode, part 7 fix full linear pitch on OPL, OPLL, SAA1099 and Sound Unit --- src/engine/platform/opl.cpp | 6 +++--- src/engine/platform/opll.cpp | 4 ++-- src/engine/platform/saa.cpp | 4 ++-- src/engine/platform/su.cpp | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index b95b42edd..0933934c8 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -800,19 +800,19 @@ int DivPlatformOPL::dispatch(DivCommand c) { int newFreq; bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq+c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq)); if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq-c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq)); if (newFreq<=destFreq) { newFreq=destFreq; return2=true; } } - if (!chan[c.chan].portaPause) { + if (!chan[c.chan].portaPause && parent->song.linearPitch!=2) { if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { chan[c.chan].portaPause=true; break; diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 89b413179..50eab344f 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -578,13 +578,13 @@ int DivPlatformOPLL::dispatch(DivCommand c) { int newFreq; bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq+c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq)); if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); + newFreq=chan[c.chan].baseFreq-c.value*((parent->song.linearPitch==2)?1:octave(chan[c.chan].baseFreq)); if (newFreq<=destFreq) { newFreq=destFreq; return2=true; diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index b662b1e35..ec5f4f6de 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -311,13 +311,13 @@ int DivPlatformSAA1099::dispatch(DivCommand c) { int destFreq=NOTE_PERIODIC(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value*(8-chan[c.chan].freqH); + chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(8-chan[c.chan].freqH)); if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value*(8-chan[c.chan].freqH); + chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:(8-chan[c.chan].freqH)); if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 4630f90e6..4af605547 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -298,13 +298,13 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) { int destFreq=NOTE_FREQUENCY(c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - chan[c.chan].baseFreq+=c.value*(1+(chan[c.chan].baseFreq>>9)); + chan[c.chan].baseFreq+=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9))); if (chan[c.chan].baseFreq>=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; } } else { - chan[c.chan].baseFreq-=c.value*(1+(chan[c.chan].baseFreq>>9)); + chan[c.chan].baseFreq-=c.value*((parent->song.linearPitch==2)?1:(1+(chan[c.chan].baseFreq>>9))); if (chan[c.chan].baseFreq<=destFreq) { chan[c.chan].baseFreq=destFreq; return2=true; From 14517c9cf9847c790281f3000e7dbcbfd92481e2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 00:51:53 -0500 Subject: [PATCH 18/68] full linear pitch is now the default the future --- src/engine/song.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/song.h b/src/engine/song.h index 489b744bb..4c88794d9 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -417,7 +417,7 @@ struct DivSong { masterVol(1.0f), tuning(440.0f), limitSlides(false), - linearPitch(1), + linearPitch(2), pitchSlideSpeed(4), loopModality(0), properNoiseLayout(false), From 0c0a97c21b9d641a6b0c012e4b1d32468c81f3e6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 01:13:21 -0500 Subject: [PATCH 19/68] SCC: address review comments --- src/engine/platform/scc.cpp | 12 ++++++------ src/engine/song.h | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index 87a42afee..125425a54 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -23,7 +23,7 @@ #define CHIP_DIVIDER 32 -#define rWrite(a,v) {if(!skipRegisterWrites) {scc->scc_w(true,a,v); regPool[a]=v; if(dumpWrites) addWrite(a,v); }} +#define rWrite(a,v) {if (!skipRegisterWrites) {scc->scc_w(true,a,v); regPool[a]=v; if (dumpWrites) addWrite(a,v); }} const char* regCheatSheetSCC[]={ "Ch1_Wave", "00", @@ -153,11 +153,11 @@ void DivPlatformSCC::tick(bool sysTick) { } } if (chan[i].freqChanged) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)+chan[i].pitch2; - if (chan[i].freq<1) chan[i].freq=1; - if (chan[i].freq>4096) chan[i].freq=4096; - rWrite(regBase+0+i*2,(chan[i].freq-1)&0xff); - rWrite(regBase+1+i*2,(chan[i].freq-1)>>8); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER)-1; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>4095) chan[i].freq=4095; + rWrite(regBase+0+i*2,chan[i].freq&0xff); + rWrite(regBase+1+i*2,chan[i].freq>>8); chan[i].freqChanged=false; } } diff --git a/src/engine/song.h b/src/engine/song.h index 4c88794d9..d8a37cbb6 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -376,7 +376,6 @@ struct DivSong { */ void clearSamples(); - /** * unloads the song, freeing all memory associated with it. * use before destroying the object. From 35ff5430d30e85bdfdbe032e495645c667877c67 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 02:20:14 -0500 Subject: [PATCH 20/68] SCC: VGM export, forceIns bug fix and 10xx --- src/engine/platform/scc.cpp | 14 +++++++---- src/engine/sysDef.cpp | 12 ++++++---- src/engine/vgmOps.cpp | 48 +++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index 125425a54..96ec2dfd2 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -21,7 +21,7 @@ #include "../engine.h" #include -#define CHIP_DIVIDER 32 +#define CHIP_DIVIDER 16 #define rWrite(a,v) {if (!skipRegisterWrites) {scc->scc_w(true,a,v); regPool[a]=v; if (dumpWrites) addWrite(a,v); }} @@ -281,7 +281,9 @@ void DivPlatformSCC::forceIns() { for (int i=0; i<5; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; - updateWave(i); + if (chan[i].active) { + updateWave(i); + } } } @@ -326,7 +328,9 @@ void DivPlatformSCC::notifyWaveChange(int wave) { for (int i=0; i<5; i++) { if (chan[i].wave==wave) { chan[i].ws.changeWave1(chan[i].wave); - updateWave(i); + if (chan[i].active) { + updateWave(i); + } } } } @@ -358,8 +362,8 @@ int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int f isMuted[i]=false; oscBuf[i]=new DivDispatchOscBuffer; } - chipClock=COLOR_NTSC; - rate=chipClock/16; + chipClock=COLOR_NTSC/2.0; + rate=chipClock/8; for (int i=0; i<5; i++) { oscBuf[i]->rate=rate; } diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index c56648406..436ca6891 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1459,11 +1459,13 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_SCC]=new DivSysDef( - "Konami SCC", NULL, 0xa1, 0, 5, false, true, 0, false, + "Konami SCC", NULL, 0xa1, 0, 5, false, true, 0x161, false, {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, {"CH1", "CH2", "CH3", "CH4", "CH5"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC} + {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}, + {}, + waveOnlyEffectHandler ); auto oplDrumsEffectHandler=[this](int ch, unsigned char effect, unsigned char effectVal) -> bool { @@ -1740,11 +1742,13 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_SCC_PLUS]=new DivSysDef( - "Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0, false, + "Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0x161, false, {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"}, {"CH1", "CH2", "CH3", "CH4", "CH5"}, {DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE, DIV_CH_WAVE}, - {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC} + {DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC, DIV_INS_SCC}, + {}, + waveOnlyEffectHandler ); sysDefs[DIV_SYSTEM_SOUND_UNIT]=new DivSysDef( diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 53800a525..f402322bd 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -598,6 +598,36 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; } break; + case DIV_SYSTEM_SCC: + if (write.addr<0x80) { + w->writeC(0xd2); + w->writeC(0); + w->writeC(baseAddr2|(write.addr&0x7f)); + w->writeC(write.val&0xff); + } else if (write.addr<0x8a) { + w->writeC(0xd2); + w->writeC(1); + w->writeC(baseAddr2|((write.addr-0x80)&0x7f)); + w->writeC(write.val&0xff); + } else if (write.addr<0x8f) { + w->writeC(0xd2); + w->writeC(2); + w->writeC(baseAddr2|((write.addr-0x8a)&0x7f)); + w->writeC(write.val&0xff); + } else if (write.addr<0x90) { + w->writeC(0xd2); + w->writeC(3); + w->writeC(baseAddr2|((write.addr-0x8f)&0x7f)); + w->writeC(write.val&0xff); + } else if (write.addr>=0xe0) { + w->writeC(0xd2); + w->writeC(5); + w->writeC(baseAddr2|((write.addr-0xe0)&0x7f)); + w->writeC(write.val&0xff); + } else { + logW("SCC: writing to unmapped address %.2x!",write.addr); + } + break; default: logW("write not handled!"); break; @@ -988,6 +1018,24 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { howManyChips++; } break; + case DIV_SYSTEM_SCC: + case DIV_SYSTEM_SCC_PLUS: + if (!hasK051649) { + hasK051649=disCont[i].dispatch->chipClock; + if (song.system[i]==DIV_SYSTEM_SCC_PLUS) { + hasK051649|=0x80000000; + } + willExport[i]=true; + } else if (!(hasK051649&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasK051649|=0x40000000; + if (song.system[i]==DIV_SYSTEM_SCC_PLUS) { + hasK051649|=0x80000000; + } + howManyChips++; + } + break; default: break; } From 955cd48fcfe0a88a2f2e0fe830c8eb4b2170b895 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 02:32:08 -0500 Subject: [PATCH 21/68] SCC: SCC+ and dual-SCC VGM export --- src/engine/vgmOps.cpp | 55 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index f402322bd..0a995e521 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -601,33 +601,68 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_SCC: if (write.addr<0x80) { w->writeC(0xd2); - w->writeC(0); - w->writeC(baseAddr2|(write.addr&0x7f)); + w->writeC(baseAddr2|0); + w->writeC(write.addr&0x7f); w->writeC(write.val&0xff); } else if (write.addr<0x8a) { w->writeC(0xd2); - w->writeC(1); - w->writeC(baseAddr2|((write.addr-0x80)&0x7f)); + w->writeC(baseAddr2|1); + w->writeC((write.addr-0x80)&0x7f); w->writeC(write.val&0xff); } else if (write.addr<0x8f) { w->writeC(0xd2); - w->writeC(2); - w->writeC(baseAddr2|((write.addr-0x8a)&0x7f)); + w->writeC(baseAddr2|2); + w->writeC((write.addr-0x8a)&0x7f); w->writeC(write.val&0xff); } else if (write.addr<0x90) { w->writeC(0xd2); - w->writeC(3); - w->writeC(baseAddr2|((write.addr-0x8f)&0x7f)); + w->writeC(baseAddr2|3); + w->writeC((write.addr-0x8f)&0x7f); w->writeC(write.val&0xff); } else if (write.addr>=0xe0) { w->writeC(0xd2); - w->writeC(5); - w->writeC(baseAddr2|((write.addr-0xe0)&0x7f)); + w->writeC(baseAddr2|5); + w->writeC((write.addr-0xe0)&0x7f); w->writeC(write.val&0xff); } else { logW("SCC: writing to unmapped address %.2x!",write.addr); } break; + case DIV_SYSTEM_SCC_PLUS: + if (write.addr<0x80) { + w->writeC(0xd2); + w->writeC(baseAddr2|0); + w->writeC(write.addr&0x7f); + w->writeC(write.val&0xff); + } else if (write.addr<0xa0) { + w->writeC(0xd2); + w->writeC(baseAddr2|4); + w->writeC(write.addr); + w->writeC(write.val&0xff); + } else if (write.addr<0xaa) { + w->writeC(0xd2); + w->writeC(baseAddr2|1); + w->writeC((write.addr-0xa0)&0x7f); + w->writeC(write.val&0xff); + } else if (write.addr<0xaf) { + w->writeC(0xd2); + w->writeC(baseAddr2|2); + w->writeC((write.addr-0xaa)&0x7f); + w->writeC(write.val&0xff); + } else if (write.addr<0xb0) { + w->writeC(0xd2); + w->writeC(baseAddr2|3); + w->writeC((write.addr-0xaf)&0x7f); + w->writeC(write.val&0xff); + } else if (write.addr>=0xe0) { + w->writeC(0xd2); + w->writeC(baseAddr2|5); + w->writeC((write.addr-0xe0)&0x7f); + w->writeC(write.val&0xff); + } else { + logW("SCC+: writing to unmapped address %.2x!",write.addr); + } + break; default: logW("write not handled!"); break; From 431b6b595765d7693b8aee2fac38ea26a6908338 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 02:42:05 -0500 Subject: [PATCH 22/68] fix 09xx, 0Bxx, 0Dxx and 0Fxx being ignored on ED --- src/engine/playback.cpp | 79 +++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index feec7d961..6a27dc65e 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -268,26 +268,53 @@ void DivEngine::processRow(int i, bool afterDelay) { int whatRow=afterDelay?chan[i].delayRow:curRow; DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][whatOrder],false); // pre effects - if (!afterDelay) for (int j=0; jdata[whatRow][4+(j<<1)]; - short effectVal=pat->data[whatRow][5+(j<<1)]; + if (!afterDelay) { + bool returnAfterPre=false; + for (int j=0; jdata[whatRow][4+(j<<1)]; + short effectVal=pat->data[whatRow][5+(j<<1)]; - if (effectVal==-1) effectVal=0; - if (effect==0xed && effectVal!=0) { - if (effectVal<=nextSpeed) { - chan[i].rowDelay=effectVal+1; - chan[i].delayOrder=whatOrder; - chan[i].delayRow=whatRow; - if (effectVal==nextSpeed) { - //if (sysOfChan[i]!=DIV_SYSTEM_YM2610 && sysOfChan[i]!=DIV_SYSTEM_YM2610_EXT) chan[i].delayLocked=true; - } else { - chan[i].delayLocked=false; - } - return; - } else { - chan[i].delayLocked=false; + if (effectVal==-1) effectVal=0; + + switch (effect) { + case 0x09: // speed 1 + if (effectVal>0) speed1=effectVal; + break; + case 0x0f: // speed 2 + if (effectVal>0) speed2=effectVal; + break; + case 0x0b: // change order + if (changeOrd==-1) { + changeOrd=effectVal; + changePos=0; + } + break; + case 0x0d: // next order + if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) { + changeOrd=-2; + changePos=effectVal; + } + break; + case 0xed: // delay + if (effectVal!=0) { + if (effectVal<=nextSpeed) { + chan[i].rowDelay=effectVal+1; + chan[i].delayOrder=whatOrder; + chan[i].delayRow=whatRow; + if (effectVal==nextSpeed) { + //if (sysOfChan[i]!=DIV_SYSTEM_YM2610 && sysOfChan[i]!=DIV_SYSTEM_YM2610_EXT) chan[i].delayLocked=true; + } else { + chan[i].delayLocked=false; + } + returnAfterPre=true; + } else { + chan[i].delayLocked=false; + } + } + break; } } + if (returnAfterPre) return; } if (chan[i].delayLocked) return; @@ -386,24 +413,6 @@ void DivEngine::processRow(int i, bool afterDelay) { // per-system effect if (!perSystemEffect(i,effect,effectVal)) switch (effect) { - case 0x09: // speed 1 - if (effectVal>0) speed1=effectVal; - break; - case 0x0f: // speed 2 - if (effectVal>0) speed2=effectVal; - break; - case 0x0b: // change order - if (changeOrd==-1) { - changeOrd=effectVal; - changePos=0; - } - break; - case 0x0d: // next order - if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) { - changeOrd=-2; - changePos=effectVal; - } - break; case 0x08: // panning (split 4-bit) chan[i].panL=(effectVal>>4)|(effectVal&0xf0); chan[i].panR=(effectVal&15)|((effectVal&15)<<4); From e48faa4114c61f4272279c6e50dc426ea2350210 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Wed, 11 May 2022 15:22:17 +0700 Subject: [PATCH 23/68] VERA: halve output volume to match other PSGs In terms of maximum single-channel output. X16's mixing is kept by halving YM2151 part. --- src/engine/platform/vera.cpp | 2 +- src/gui/presets.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 58aa55b92..65a7452ee 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -414,7 +414,7 @@ void DivPlatformVERA::muteChannel(int ch, bool mute) { } float DivPlatformVERA::getPostAmp() { - return 8.0f; + return 4.0f; } bool DivPlatformVERA::isStereo() { diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 28a57f861..efb73ec51 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -599,7 +599,7 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "Commander X16", { DIV_SYSTEM_VERA, 64, 0, 0, - DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_YM2151, 32, 0, 0, 0 } )); From ab969c1da00f89fa28cba7a39e9af9db186745e9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 03:29:03 -0500 Subject: [PATCH 24/68] add OPN chip pretty much copy-paste from YM2610B code next up: even more blatant copy-paste in 2608 --- CMakeLists.txt | 1 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/ym2203.cpp | 1132 ++++++++++++++++++++++++++++ src/engine/platform/ym2203.h | 135 ++++ src/engine/platform/ym2203shared.h | 45 ++ src/engine/sysDef.cpp | 5 +- src/gui/guiConst.cpp | 2 + src/gui/presets.cpp | 6 + 8 files changed, 1329 insertions(+), 1 deletion(-) create mode 100644 src/engine/platform/ym2203.cpp create mode 100644 src/engine/platform/ym2203.h create mode 100644 src/engine/platform/ym2203shared.h diff --git a/CMakeLists.txt b/CMakeLists.txt index db0a51a5c..b415fb99d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -345,6 +345,7 @@ src/engine/platform/nes.cpp src/engine/platform/c64.cpp src/engine/platform/arcade.cpp src/engine/platform/tx81z.cpp +src/engine/platform/ym2203.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp src/engine/platform/ym2610b.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index c04276ea7..7595d14bb 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -29,6 +29,7 @@ #include "platform/c64.h" #include "platform/arcade.h" #include "platform/tx81z.h" +#include "platform/ym2203.h" #include "platform/ym2610.h" #include "platform/ym2610ext.h" #include "platform/ym2610b.h" @@ -234,6 +235,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_TIA: dispatch=new DivPlatformTIA; break; + case DIV_SYSTEM_OPN: + dispatch=new DivPlatformYM2203; + break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp new file mode 100644 index 000000000..e18d76d2a --- /dev/null +++ b/src/engine/platform/ym2203.cpp @@ -0,0 +1,1132 @@ +/** + * 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 "ym2203.h" +#include "sound/ymfm/ymfm.h" +#include "../engine.h" +#include +#include + +#include "ym2203shared.h" + +#include "fmshared_OPN.h" + +static unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 +}; + +#define CHIP_DIVIDER 32 + +const char* regCheatSheetYM2203[]={ + // SSG + "SSG_FreqL_A", "000", + "SSG_FreqH_A", "001", + "SSG_FreqL_B", "002", + "SSG_FreqH_B", "003", + "SSG_FreqL_C", "004", + "SSG_FreqH_C", "005", + "SSG_FreqNoise", "006", + "SSG_Enable", "007", + "SSG_Volume_A", "008", + "SSG_Volume_B", "009", + "SSG_Volume_C", "00A", + "SSG_FreqL_Env", "00B", + "SSG_FreqH_Env", "00C", + "SSG_Control_Env", "00D", + // FM (Common) + "FM_Test", "021", + "FM_LFOFreq", "022", + "ClockA1", "024", + "ClockA2", "025", + "ClockB", "026", + "FM_Control", "027", + "FM_NoteCtl", "028", + // FM (Channel 1-3) + "FM1_Op1_DT_MULT", "030", + "FM2_Op1_DT_MULT", "031", + "FM3_Op1_DT_MULT", "032", + "FM1_Op2_DT_MULT", "034", + "FM2_Op2_DT_MULT", "035", + "FM3_Op2_DT_MULT", "036", + "FM1_Op3_DT_MULT", "038", + "FM2_Op3_DT_MULT", "039", + "FM3_Op3_DT_MULT", "03A", + "FM1_Op4_DT_MULT", "03C", + "FM2_Op4_DT_MULT", "03D", + "FM3_Op4_DT_MULT", "03E", + "FM1_Op1_TL", "040", + "FM2_Op1_TL", "041", + "FM3_Op1_TL", "042", + "FM1_Op2_TL", "044", + "FM2_Op2_TL", "045", + "FM3_Op2_TL", "046", + "FM1_Op3_TL", "048", + "FM2_Op3_TL", "049", + "FM3_Op3_TL", "04A", + "FM1_Op4_TL", "04C", + "FM2_Op4_TL", "04D", + "FM3_Op4_TL", "04E", + "FM1_Op1_KS_AR", "050", + "FM2_Op1_KS_AR", "051", + "FM3_Op1_KS_AR", "052", + "FM1_Op2_KS_AR", "054", + "FM2_Op2_KS_AR", "055", + "FM3_Op2_KS_AR", "056", + "FM1_Op3_KS_AR", "058", + "FM2_Op3_KS_AR", "059", + "FM3_Op3_KS_AR", "05A", + "FM1_Op4_KS_AR", "05C", + "FM2_Op4_KS_AR", "05D", + "FM3_Op4_KS_AR", "05E", + "FM1_Op1_AM_DR", "060", + "FM2_Op1_AM_DR", "061", + "FM3_Op1_AM_DR", "062", + "FM1_Op2_AM_DR", "064", + "FM2_Op2_AM_DR", "065", + "FM3_Op2_AM_DR", "066", + "FM1_Op3_AM_DR", "068", + "FM2_Op3_AM_DR", "069", + "FM3_Op3_AM_DR", "06A", + "FM1_Op4_AM_DR", "06C", + "FM2_Op4_AM_DR", "06D", + "FM3_Op4_AM_DR", "06E", + "FM1_Op1_SR", "070", + "FM2_Op1_SR", "071", + "FM3_Op1_SR", "072", + "FM1_Op2_SR", "074", + "FM2_Op2_SR", "075", + "FM3_Op2_SR", "076", + "FM1_Op3_SR", "078", + "FM2_Op3_SR", "079", + "FM3_Op3_SR", "07A", + "FM1_Op4_SR", "07C", + "FM2_Op4_SR", "07D", + "FM3_Op4_SR", "07E", + "FM1_Op1_SL_RR", "080", + "FM2_Op1_SL_RR", "081", + "FM3_Op1_SL_RR", "082", + "FM1_Op2_SL_RR", "084", + "FM2_Op2_SL_RR", "085", + "FM3_Op2_SL_RR", "086", + "FM1_Op3_SL_RR", "088", + "FM2_Op3_SL_RR", "089", + "FM3_Op3_SL_RR", "08A", + "FM1_Op4_SL_RR", "08C", + "FM2_Op4_SL_RR", "08D", + "FM3_Op4_SL_RR", "08E", + "FM1_Op1_SSG_EG", "090", + "FM2_Op1_SSG_EG", "091", + "FM3_Op1_SSG_EG", "092", + "FM1_Op2_SSG_EG", "094", + "FM2_Op2_SSG_EG", "095", + "FM3_Op2_SSG_EG", "096", + "FM1_Op3_SSG_EG", "098", + "FM2_Op3_SSG_EG", "099", + "FM3_Op3_SSG_EG", "09A", + "FM1_Op4_SSG_EG", "09C", + "FM2_Op4_SSG_EG", "09D", + "FM3_Op4_SSG_EG", "09E", + "FM1_FNum1", "0A0", + "FM2_FNum1", "0A1", + "FM3_(Op1)FNum1", "0A2", + "FM1_FNum2", "0A4", + "FM2_FNum2", "0A5", + "FM3_(Op1)FNum2", "0A6", + "FM3_Op2_FNum1", "0A8", + "FM3_Op3_FNum1", "0A9", + "FM3_Op4_FNum1", "0AA", + "FM3_Op2_FNum2", "0AC", + "FM3_Op3_FNum2", "0AD", + "FM3_Op4_FNum2", "0AE", + "FM1_FB_ALG", "0B0", + "FM2_FB_ALG", "0B1", + "FM3_FB_ALG", "0B2", + "FM1_Pan_LFO", "0B4", + "FM2_Pan_LFO", "0B5", + "FM3_Pan_LFO", "0B6", + NULL +}; + +const char** DivPlatformYM2203::getRegisterSheet() { + return regCheatSheetYM2203; +} + +const char* DivPlatformYM2203::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xy: Setup LFO (x: enable; y: speed)"; + break; + case 0x11: + return "11xx: Set feedback (0 to 7)"; + break; + case 0x12: + return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; + break; + case 0x13: + return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; + break; + case 0x14: + return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; + break; + case 0x15: + return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; + break; + case 0x16: + return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; + break; + case 0x18: + return "18xx: Toggle extended channel 3 mode"; + break; + case 0x19: + return "19xx: Set attack of all operators (0 to 1F)"; + break; + case 0x1a: + return "1Axx: Set attack of operator 1 (0 to 1F)"; + break; + case 0x1b: + return "1Bxx: Set attack of operator 2 (0 to 1F)"; + break; + case 0x1c: + return "1Cxx: Set attack of operator 3 (0 to 1F)"; + break; + case 0x1d: + return "1Dxx: Set attack of operator 4 (0 to 1F)"; + break; + case 0x20: + return "20xx: Set SSG channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; + break; + case 0x21: + return "21xx: Set SSG noise frequency (0 to 1F)"; + break; + case 0x22: + return "22xy: Set SSG envelope mode (x: shape, y: enable for this channel)"; + break; + case 0x23: + return "23xx: Set SSG envelope period low byte"; + break; + case 0x24: + return "24xx: Set SSG envelope period high byte"; + break; + case 0x25: + return "25xx: SSG envelope slide up"; + break; + case 0x26: + return "26xx: SSG envelope slide down"; + break; + case 0x29: + return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; + break; + case 0x30: + return "30xx: Toggle hard envelope reset on new notes"; + break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; + break; + case 0x54: + return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; + break; + case 0x55: + return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to 1F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to 1F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to 1F)"; + break; + case 0x59: + return "59xx: Set decay of operator 3 (0 to 1F)"; + break; + case 0x5a: + return "5Axx: Set decay of operator 4 (0 to 1F)"; + break; + case 0x5b: + return "5Bxx: Set decay 2 of all operators (0 to 1F)"; + break; + case 0x5c: + return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; + break; + case 0x5d: + return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; + break; + case 0x5e: + return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; + break; + case 0x5f: + return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; + break; + } + return NULL; +} + +double DivPlatformYM2203::NOTE_OPN(int ch, int note) { + // FM + return NOTE_FNUM_BLOCK(note,11); +} + +void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t len) { + static int os; + + /*ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); + ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); + ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + + ymfm::ssg_engine::output_data ssgOut; + + ymfm::fm_channel>* fmChan[6]; + ymfm::adpcm_a_channel* adpcmAChan[6]; + for (int i=0; i<6; i++) { + fmChan[i]=fme->debug_channel(i); + adpcmAChan[i]=aae->debug_channel(i); + }*/ + + for (size_t h=start; hwrite(0x0+((w.addr>>8)<<1),w.addr); + fm->write(0x1+((w.addr>>8)<<1),w.val); + regPool[w.addr&0x1ff]=w.val; + writes.pop(); + delay=16; + } + } + + fm->generate(&fmout); + + os=fmout.data[0]+((fmout.data[1]+fmout.data[2]+fmout.data[3])>>1); + if (os<-32768) os=-32768; + if (os>32767) os=32767; + + bufL[h]=os; + + /* + for (int i=0; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + } + + ssge->get_last_out(ssgOut); + for (int i=6; i<9; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]; + } + + for (int i=9; i<15; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1); + } + + oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + */ + } +} + +void DivPlatformYM2203::tick(bool sysTick) { + // PSG + ay->tick(sysTick); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + + // FM + for (int i=0; i<3; i++) { + if (i==2 && extMode) continue; + chan[i].std.next(); + + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); + } else { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); + chan[i].freqChanged=true; + } + } + + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&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].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + + if (chan[i].std.alg.had) { + chan[i].state.alg=chan[i].std.alg.val; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + } + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } + if (chan[i].std.fms.had) { + chan[i].state.fms=chan[i].std.fms.val; + 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].std.ams.had) { + chan[i].state.ams=chan[i].std.ams.val; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + if (m.am.had) { + op.am=m.am.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.ar.had) { + op.ar=m.ar.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dr.had) { + op.dr=m.dr.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.mult.had) { + op.mult=m.mult.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.rr.had) { + op.rr=m.rr.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.sl.had) { + op.sl=m.sl.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.tl.had) { + op.tl=127-m.tl.val; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (m.rs.had) { + op.rs=m.rs.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dt.had) { + op.dt=m.dt.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.d2r.had) { + op.d2r=m.d2r.val; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + + if (chan[i].keyOn || chan[i].keyOff) { + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + immWrite(baseAddr+ADDR_SL_RR,0x0f); + immWrite(baseAddr+ADDR_TL,0x7f); + oldWrites[baseAddr+ADDR_SL_RR]=-1; + oldWrites[baseAddr+ADDR_TL]=-1; + //rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } + immWrite(0x28,0x00|konOffs[i]); + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + for (int k=0; k<100; k++) { + immWrite(baseAddr+ADDR_SL_RR,0x0f); + } + } + } + chan[i].keyOff=false; + } + } + + for (int i=16; i<512; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } + + for (int i=0; i<3; i++) { + if (i==2 && extMode) continue; + if (chan[i].freqChanged) { + if (parent->song.linearPitch==2) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); + } else { + int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2); + int block=(chan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; + } + chan[i].freq=(block<<11)|fNum; + } + if (chan[i].freq>0x3fff) chan[i].freq=0x3fff; + immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); + immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); + chan[i].freqChanged=false; + } + if (chan[i].keyOn) { + immWrite(0x28,0xf0|konOffs[i]); + chan[i].keyOn=false; + } + } +} + +int DivPlatformYM2203::dispatch(DivCommand c) { + if (c.chan>2) { + c.chan-=3; + return ay->dispatch(c); + } + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + chan[c.chan].macroInit(ins); + if (c.chan<3) { + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + } + + if (chan[c.chan].insChanged) { + chan[c.chan].state=ins->fm; + } + + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + if (chan[c.chan].insChanged) { + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + 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)); + } + chan[c.chan].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].keyOn=true; + chan[c.chan].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + 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]; + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + break; + } + case DIV_CMD_GET_VOLUME: { + return chan[c.chan].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].insChanged=true; + } + chan[c.chan].ins=c.value; + break; + case DIV_CMD_PANNING: { + if (c.value==0 && c.value2==0) { + chan[c.chan].pan=3; + } else { + chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); + } + 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; + } + case DIV_CMD_PITCH: { + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + if (c.chan>2 || parent->song.linearPitch==2) { // PSG + int destFreq=NOTE_OPN(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; + } + int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); + int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); + int newFreq; + bool return2=false; + if (chan[c.chan].portaPause) { + chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq; + } + if (destFreq>chan[c.chan].baseFreq) { + newFreq=chan[c.chan].baseFreq+c.value; + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=chan[c.chan].baseFreq-c.value; + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + // check for octave boundary + // what the heck! + if (!chan[c.chan].portaPause) { + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { + chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + if ((newFreq&0x7ff)0) { + chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + } + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + chan[c.chan].baseFreq=newFreq; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_OPN(c.chan,c.value); + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_FM_LFO: { + rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + break; + } + case DIV_CMD_FM_FB: { + if (c.chan>2) break; + chan[c.chan].state.fb=c.value&7; + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + break; + } + case DIV_CMD_FM_MULT: { + if (c.chan>2) break; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + break; + } + case DIV_CMD_FM_TL: { + if (c.chan>2) break; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + break; + } + case DIV_CMD_FM_AR: { + if (c.chan>2) break; + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } + case DIV_CMD_FM_SSG: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + break; + } + case DIV_CMD_FM_HARD_RESET: + chan[c.chan].hardReset=c.value; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + if (c.chan>2) return 15; + return 127; + break; + case DIV_CMD_PRE_PORTA: + if (c.chan>2) { + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); + } + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformYM2203::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (ch>2) { // PSG + ay->muteChannel(ch-3,mute); + return; + } + // FM + rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); +} + +void DivPlatformYM2203::forceIns() { + for (int i=0; i<3; i++) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + 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; + } + } + for (int i=3; i<6; i++) { + chan[i].insChanged=true; + } + + ay->forceIns(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); +} + +void* DivPlatformYM2203::getChanState(int ch) { + return &chan[ch]; +} + +DivDispatchOscBuffer* DivPlatformYM2203::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformYM2203::getRegisterPool() { + return regPool; +} + +int DivPlatformYM2203::getRegisterPoolSize() { + return 512; +} + +void DivPlatformYM2203::poke(unsigned int addr, unsigned short val) { + immWrite(addr,val); +} + +void DivPlatformYM2203::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +void DivPlatformYM2203::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,512); + if (dumpWrites) { + addWrite(0xffffffff,0); + } + fm->reset(); + for (int i=0; i<6; i++) { + chan[i]=DivPlatformYM2203::Channel(); + chan[i].std.setEngine(parent); + } + for (int i=0; i<3; i++) { + chan[i].vol=0x7f; + chan[i].outVol=0x7f; + } + for (int i=3; i<6; i++) { + chan[i].vol=0x0f; + } + + for (int i=0; i<512; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + sampleBank=0; + + delay=0; + + extMode=false; + + // LFO + immWrite(0x22,0x08); + + ay->reset(); + ay->getRegisterWrites().clear(); + ay->flushWrites(); +} + +bool DivPlatformYM2203::isStereo() { + return false; +} + +bool DivPlatformYM2203::keyOffAffectsArp(int ch) { + return (ch>2); +} + +void DivPlatformYM2203::notifyInsChange(int ins) { + for (int i=0; i<6; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } + ay->notifyInsChange(ins); +} + +void DivPlatformYM2203::notifyInsDeletion(void* ins) { + ay->notifyInsDeletion(ins); +} + +void DivPlatformYM2203::setSkipRegisterWrites(bool value) { + DivDispatch::setSkipRegisterWrites(value); + ay->setSkipRegisterWrites(value); +} + +void DivPlatformYM2203::setFlags(unsigned int flags) { + unsigned char ayFlags=32; + if (flags==3) { + chipClock=3000000.0; + ayFlags=36; + } else if (flags==2) { + chipClock=4000000.0; + ayFlags=35; + } else if (flags==1) { + chipClock=COLOR_PAL*4.0/5.0; + ayFlags=33; + } else { + chipClock=COLOR_NTSC; + ayFlags=32; + } + ay->setFlags(ayFlags); + rate=fm->sample_rate(chipClock); + for (int i=0; i<6; i++) { + oscBuf[i]->rate=rate; + } +} + +int DivPlatformYM2203::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<6; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + fm=new ymfm::ym2203(iface); + // YM2149, 2MHz + ay=new DivPlatformAY8910; + ay->init(p,3,sugRate,35); + ay->toggleRegisterDump(true); + setFlags(flags); + + reset(); + return 16; +} + +void DivPlatformYM2203::quit() { + for (int i=0; i<6; i++) { + delete oscBuf[i]; + } + ay->quit(); + delete ay; + delete fm; +} + +DivPlatformYM2203::~DivPlatformYM2203() { +} diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h new file mode 100644 index 000000000..498963ab4 --- /dev/null +++ b/src/engine/platform/ym2203.h @@ -0,0 +1,135 @@ +/** + * 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 _YM2203_H +#define _YM2203_H +#include "../dispatch.h" +#include "../macroInt.h" +#include +#include "sound/ymfm/ymfm_opn.h" + +#include "ay.h" + +class DivYM2203Interface: public ymfm::ymfm_interface { + +}; + +class DivPlatformYM2203: public DivDispatch { + protected: + const unsigned short chanOffs[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 + }; + + struct Channel { + DivInstrumentFM state; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; + unsigned char psgMode, autoEnvNum, autoEnvDen; + signed char konCycles; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + int vol, outVol; + int sample; + unsigned char pan; + DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + portaPauseFreq(0), + note(0), + ins(-1), + psgMode(1), + autoEnvNum(0), + autoEnvDen(0), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + inPorta(false), + furnacePCM(false), + hardReset(false), + vol(0), + outVol(15), + sample(-1), + pan(3) {} + }; + Channel chan[6]; + DivDispatchOscBuffer* oscBuf[6]; + bool isMuted[6]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; + ymfm::ym2203* fm; + ymfm::ym2203::output_data fmout; + DivYM2203Interface iface; + unsigned char regPool[512]; + unsigned char lastBusy; + + DivPlatformAY8910* ay; + unsigned char sampleBank; + + int delay; + + bool extMode; + + short oldWrites[512]; + short pendingWrites[512]; + + double NOTE_OPN(int ch, int note); + 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); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + void setSkipRegisterWrites(bool val); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + void setFlags(unsigned int flags); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformYM2203(); +}; +#endif diff --git a/src/engine/platform/ym2203shared.h b/src/engine/platform/ym2203shared.h new file mode 100644 index 000000000..5aec79161 --- /dev/null +++ b/src/engine/platform/ym2203shared.h @@ -0,0 +1,45 @@ +/** + * 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. + */ + +static unsigned short opOffs[4]={ + 0x00, 0x04, 0x08, 0x0c +}; +static bool isOutput[8][4]={ + // 1 3 2 4 + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,true ,true}, + {false,true ,true ,true}, + {false,true ,true ,true}, + {true ,true ,true ,true}, +}; +static unsigned char dtTable[8]={ + 7,6,5,0,1,2,3,4 +}; + +static int orderedOps[4]={ + 0,2,1,3 +}; + +#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 CHIP_FREQBASE 4720270 diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 436ca6891..c16b7312d 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1263,7 +1263,10 @@ void DivEngine::registerSystems() { {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, {"F1", "F2", "F3", "S1", "S2", "S3"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, - {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY} + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler ); sysDefs[DIV_SYSTEM_PC98]=new DivSysDef( diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 792f16d5f..4ea2de0c2 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -834,6 +834,8 @@ const int availableSystems[]={ DIV_SYSTEM_YMU759, DIV_SYSTEM_DUMMY, DIV_SYSTEM_SOUND_UNIT, + DIV_SYSTEM_OPN, + DIV_SYSTEM_PC98, DIV_SYSTEM_OPLL, DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_VRC7, diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 28a57f861..2e7eb9cfd 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -439,6 +439,12 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC-98 (with PC-9801-26K)", { + DIV_SYSTEM_OPN, 64, 0, 3, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "ZX Spectrum (48K)", { DIV_SYSTEM_AY8910, 64, 0, 2, From ff0319780e83f7f4a5f2d487a4e8ebdcbf3daa2c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 03:41:02 -0500 Subject: [PATCH 25/68] OPN: VGM export - good night --- src/engine/platform/ym2203.cpp | 27 +- src/engine/platform/ym2203.h | 9 +- src/engine/platform/ym2608.cpp | 1449 ++++++++++++++++++++++++++++++++ src/engine/platform/ym2608.h | 130 +++ src/engine/sysDef.cpp | 2 +- src/engine/vgmOps.cpp | 17 + src/gui/sysConf.cpp | 15 + 7 files changed, 1627 insertions(+), 22 deletions(-) create mode 100644 src/engine/platform/ym2608.cpp create mode 100644 src/engine/platform/ym2608.h diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index e18d76d2a..83233e8f5 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -27,8 +27,8 @@ #include "fmshared_OPN.h" -static unsigned char konOffs[6]={ - 0, 1, 2, 4, 5, 6 +static unsigned char konOffs[3]={ + 0, 1, 2 }; #define CHIP_DIVIDER 32 @@ -287,11 +287,6 @@ const char* DivPlatformYM2203::getEffectName(unsigned char effect) { return NULL; } -double DivPlatformYM2203::NOTE_OPN(int ch, int note) { - // FM - return NOTE_FNUM_BLOCK(note,11); -} - void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os; @@ -314,9 +309,9 @@ void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t l if (!writes.empty()) { if (--delay<1) { QueuedWrite& w=writes.front(); - fm->write(0x0+((w.addr>>8)<<1),w.addr); - fm->write(0x1+((w.addr>>8)<<1),w.val); - regPool[w.addr&0x1ff]=w.val; + fm->write(0x0,w.addr); + fm->write(0x1,w.val); + regPool[w.addr&0xff]=w.val; writes.pop(); delay=16; } @@ -520,7 +515,7 @@ void DivPlatformYM2203::tick(bool sysTick) { } } - for (int i=16; i<512; i++) { + for (int i=16; i<256; i++) { if (pendingWrites[i]!=oldWrites[i]) { immWrite(i,pendingWrites[i]&0xff); oldWrites[i]=pendingWrites[i]; @@ -670,7 +665,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) { } case DIV_CMD_NOTE_PORTA: { if (c.chan>2 || parent->song.linearPitch==2) { // PSG - int destFreq=NOTE_OPN(c.chan,c.value2); + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -737,7 +732,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: { - chan[c.chan].baseFreq=NOTE_OPN(c.chan,c.value); + chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); chan[c.chan].freqChanged=true; break; } @@ -1002,7 +997,7 @@ unsigned char* DivPlatformYM2203::getRegisterPool() { } int DivPlatformYM2203::getRegisterPoolSize() { - return 512; + return 256; } void DivPlatformYM2203::poke(unsigned int addr, unsigned short val) { @@ -1015,7 +1010,7 @@ void DivPlatformYM2203::poke(std::vector& wlist) { void DivPlatformYM2203::reset() { while (!writes.empty()) writes.pop(); - memset(regPool,0,512); + memset(regPool,0,256); if (dumpWrites) { addWrite(0xffffffff,0); } @@ -1032,7 +1027,7 @@ void DivPlatformYM2203::reset() { chan[i].vol=0x0f; } - for (int i=0; i<512; i++) { + for (int i=0; i<256; i++) { oldWrites[i]=-1; pendingWrites[i]=-1; } diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h index 498963ab4..1a6dad584 100644 --- a/src/engine/platform/ym2203.h +++ b/src/engine/platform/ym2203.h @@ -32,8 +32,8 @@ class DivYM2203Interface: public ymfm::ymfm_interface { class DivPlatformYM2203: public DivDispatch { protected: - const unsigned short chanOffs[6]={ - 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 + const unsigned short chanOffs[3]={ + 0x00, 0x01, 0x02 }; struct Channel { @@ -101,10 +101,9 @@ class DivPlatformYM2203: public DivDispatch { bool extMode; - short oldWrites[512]; - short pendingWrites[512]; + short oldWrites[256]; + short pendingWrites[256]; - double NOTE_OPN(int ch, int note); friend void putDispatchChan(void*,int,int); public: diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp new file mode 100644 index 000000000..3b3d55767 --- /dev/null +++ b/src/engine/platform/ym2608.cpp @@ -0,0 +1,1449 @@ +/** + * 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 "ym2608.h" +#include "sound/ymfm/ymfm.h" +#include "../engine.h" +#include +#include + +#include "ym2610shared.h" + +#include "fmshared_OPN.h" + +static unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 +}; + +#define CHIP_DIVIDER 32 + +const char* regCheatSheetYM2610B[]={ + // SSG + "SSG_FreqL_A", "000", + "SSG_FreqH_A", "001", + "SSG_FreqL_B", "002", + "SSG_FreqH_B", "003", + "SSG_FreqL_C", "004", + "SSG_FreqH_C", "005", + "SSG_FreqNoise", "006", + "SSG_Enable", "007", + "SSG_Volume_A", "008", + "SSG_Volume_B", "009", + "SSG_Volume_C", "00A", + "SSG_FreqL_Env", "00B", + "SSG_FreqH_Env", "00C", + "SSG_Control_Env", "00D", + // ADPCM-B + "ADPCMB_Control", "010", + "ADPCMB_L_R", "011", + "ADPCMB_StartL", "012", + "ADPCMB_StartH", "013", + "ADPCMB_EndL", "014", + "ADPCMB_EndH", "015", + "ADPCMB_FreqL", "019", + "ADPCMB_FreqH", "01A", + "ADPCMB_Volume", "01B", + "ADPCM_Flag", "01C", + // FM (Common) + "FM_Test", "021", + "FM_LFOFreq", "022", + "ClockA1", "024", + "ClockA2", "025", + "ClockB", "026", + "FM_Control", "027", + "FM_NoteCtl", "028", + // FM (Channel 1-3) + "FM1_Op1_DT_MULT", "030", + "FM2_Op1_DT_MULT", "031", + "FM3_Op1_DT_MULT", "032", + "FM1_Op2_DT_MULT", "034", + "FM2_Op2_DT_MULT", "035", + "FM3_Op2_DT_MULT", "036", + "FM1_Op3_DT_MULT", "038", + "FM2_Op3_DT_MULT", "039", + "FM3_Op3_DT_MULT", "03A", + "FM1_Op4_DT_MULT", "03C", + "FM2_Op4_DT_MULT", "03D", + "FM3_Op4_DT_MULT", "03E", + "FM1_Op1_TL", "040", + "FM2_Op1_TL", "041", + "FM3_Op1_TL", "042", + "FM1_Op2_TL", "044", + "FM2_Op2_TL", "045", + "FM3_Op2_TL", "046", + "FM1_Op3_TL", "048", + "FM2_Op3_TL", "049", + "FM3_Op3_TL", "04A", + "FM1_Op4_TL", "04C", + "FM2_Op4_TL", "04D", + "FM3_Op4_TL", "04E", + "FM1_Op1_KS_AR", "050", + "FM2_Op1_KS_AR", "051", + "FM3_Op1_KS_AR", "052", + "FM1_Op2_KS_AR", "054", + "FM2_Op2_KS_AR", "055", + "FM3_Op2_KS_AR", "056", + "FM1_Op3_KS_AR", "058", + "FM2_Op3_KS_AR", "059", + "FM3_Op3_KS_AR", "05A", + "FM1_Op4_KS_AR", "05C", + "FM2_Op4_KS_AR", "05D", + "FM3_Op4_KS_AR", "05E", + "FM1_Op1_AM_DR", "060", + "FM2_Op1_AM_DR", "061", + "FM3_Op1_AM_DR", "062", + "FM1_Op2_AM_DR", "064", + "FM2_Op2_AM_DR", "065", + "FM3_Op2_AM_DR", "066", + "FM1_Op3_AM_DR", "068", + "FM2_Op3_AM_DR", "069", + "FM3_Op3_AM_DR", "06A", + "FM1_Op4_AM_DR", "06C", + "FM2_Op4_AM_DR", "06D", + "FM3_Op4_AM_DR", "06E", + "FM1_Op1_SR", "070", + "FM2_Op1_SR", "071", + "FM3_Op1_SR", "072", + "FM1_Op2_SR", "074", + "FM2_Op2_SR", "075", + "FM3_Op2_SR", "076", + "FM1_Op3_SR", "078", + "FM2_Op3_SR", "079", + "FM3_Op3_SR", "07A", + "FM1_Op4_SR", "07C", + "FM2_Op4_SR", "07D", + "FM3_Op4_SR", "07E", + "FM1_Op1_SL_RR", "080", + "FM2_Op1_SL_RR", "081", + "FM3_Op1_SL_RR", "082", + "FM1_Op2_SL_RR", "084", + "FM2_Op2_SL_RR", "085", + "FM3_Op2_SL_RR", "086", + "FM1_Op3_SL_RR", "088", + "FM2_Op3_SL_RR", "089", + "FM3_Op3_SL_RR", "08A", + "FM1_Op4_SL_RR", "08C", + "FM2_Op4_SL_RR", "08D", + "FM3_Op4_SL_RR", "08E", + "FM1_Op1_SSG_EG", "090", + "FM2_Op1_SSG_EG", "091", + "FM3_Op1_SSG_EG", "092", + "FM1_Op2_SSG_EG", "094", + "FM2_Op2_SSG_EG", "095", + "FM3_Op2_SSG_EG", "096", + "FM1_Op3_SSG_EG", "098", + "FM2_Op3_SSG_EG", "099", + "FM3_Op3_SSG_EG", "09A", + "FM1_Op4_SSG_EG", "09C", + "FM2_Op4_SSG_EG", "09D", + "FM3_Op4_SSG_EG", "09E", + "FM1_FNum1", "0A0", + "FM2_FNum1", "0A1", + "FM3_(Op1)FNum1", "0A2", + "FM1_FNum2", "0A4", + "FM2_FNum2", "0A5", + "FM3_(Op1)FNum2", "0A6", + "FM3_Op2_FNum1", "0A8", + "FM3_Op3_FNum1", "0A9", + "FM3_Op4_FNum1", "0AA", + "FM3_Op2_FNum2", "0AC", + "FM3_Op3_FNum2", "0AD", + "FM3_Op4_FNum2", "0AE", + "FM1_FB_ALG", "0B0", + "FM2_FB_ALG", "0B1", + "FM3_FB_ALG", "0B2", + "FM1_Pan_LFO", "0B4", + "FM2_Pan_LFO", "0B5", + "FM3_Pan_LFO", "0B6", + // ADPCM-A + "ADPCMA_Control", "100", + "ADPCMA_MVol", "101", + "ADPCMA_Test", "102", + "ADPCMA_Ch1_Vol", "108", + "ADPCMA_Ch2_Vol", "109", + "ADPCMA_Ch3_Vol", "10A", + "ADPCMA_Ch4_Vol", "10B", + "ADPCMA_Ch5_Vol", "10C", + "ADPCMA_Ch6_Vol", "10D", + "ADPCMA_Ch1_StL", "110", + "ADPCMA_Ch2_StL", "111", + "ADPCMA_Ch3_StL", "112", + "ADPCMA_Ch4_StL", "113", + "ADPCMA_Ch5_StL", "114", + "ADPCMA_Ch6_StL", "115", + "ADPCMA_Ch1_StH", "118", + "ADPCMA_Ch2_StH", "119", + "ADPCMA_Ch3_StH", "11A", + "ADPCMA_Ch4_StH", "11B", + "ADPCMA_Ch5_StH", "11C", + "ADPCMA_Ch6_StH", "11D", + "ADPCMA_Ch1_EdL", "120", + "ADPCMA_Ch2_EdL", "121", + "ADPCMA_Ch3_EdL", "122", + "ADPCMA_Ch4_EdL", "123", + "ADPCMA_Ch5_EdL", "124", + "ADPCMA_Ch6_EdL", "125", + "ADPCMA_Ch1_EdH", "128", + "ADPCMA_Ch2_EdH", "129", + "ADPCMA_Ch3_EdH", "12A", + "ADPCMA_Ch4_EdH", "12B", + "ADPCMA_Ch5_EdH", "12C", + "ADPCMA_Ch6_EdH", "12D", + // FM (Channel 4-6) + "FM4_Op1_DT_MULT", "130", + "FM5_Op1_DT_MULT", "131", + "FM6_Op1_DT_MULT", "132", + "FM4_Op2_DT_MULT", "134", + "FM5_Op2_DT_MULT", "135", + "FM6_Op2_DT_MULT", "136", + "FM4_Op3_DT_MULT", "138", + "FM5_Op3_DT_MULT", "139", + "FM6_Op3_DT_MULT", "13A", + "FM4_Op4_DT_MULT", "13C", + "FM5_Op4_DT_MULT", "13D", + "FM6_Op4_DT_MULT", "13E", + "FM4_Op1_TL", "140", + "FM5_Op1_TL", "141", + "FM6_Op1_TL", "142", + "FM4_Op2_TL", "144", + "FM5_Op2_TL", "145", + "FM6_Op2_TL", "146", + "FM4_Op3_TL", "148", + "FM5_Op3_TL", "149", + "FM6_Op3_TL", "14A", + "FM4_Op4_TL", "14C", + "FM5_Op4_TL", "14D", + "FM6_Op4_TL", "14E", + "FM4_Op1_KS_AR", "150", + "FM5_Op1_KS_AR", "151", + "FM6_Op1_KS_AR", "152", + "FM4_Op2_KS_AR", "154", + "FM5_Op2_KS_AR", "155", + "FM6_Op2_KS_AR", "156", + "FM4_Op3_KS_AR", "158", + "FM5_Op3_KS_AR", "159", + "FM6_Op3_KS_AR", "15A", + "FM4_Op4_KS_AR", "15C", + "FM5_Op4_KS_AR", "15D", + "FM6_Op4_KS_AR", "15E", + "FM4_Op1_AM_DR", "160", + "FM5_Op1_AM_DR", "161", + "FM6_Op1_AM_DR", "162", + "FM4_Op2_AM_DR", "164", + "FM5_Op2_AM_DR", "165", + "FM6_Op2_AM_DR", "166", + "FM4_Op3_AM_DR", "168", + "FM5_Op3_AM_DR", "169", + "FM6_Op3_AM_DR", "16A", + "FM4_Op4_AM_DR", "16C", + "FM5_Op4_AM_DR", "16D", + "FM6_Op4_AM_DR", "16E", + "FM4_Op1_SR", "170", + "FM5_Op1_SR", "171", + "FM6_Op1_SR", "172", + "FM4_Op2_SR", "174", + "FM5_Op2_SR", "175", + "FM6_Op2_SR", "176", + "FM4_Op3_SR", "178", + "FM5_Op3_SR", "179", + "FM6_Op3_SR", "17A", + "FM4_Op4_SR", "17C", + "FM5_Op4_SR", "17D", + "FM6_Op4_SR", "17E", + "FM4_Op1_SL_RR", "180", + "FM5_Op1_SL_RR", "181", + "FM6_Op1_SL_RR", "182", + "FM4_Op2_SL_RR", "184", + "FM5_Op2_SL_RR", "185", + "FM6_Op2_SL_RR", "186", + "FM4_Op3_SL_RR", "188", + "FM5_Op3_SL_RR", "189", + "FM6_Op3_SL_RR", "18A", + "FM4_Op4_SL_RR", "18C", + "FM5_Op4_SL_RR", "18D", + "FM6_Op4_SL_RR", "18E", + "FM4_Op1_SSG_EG", "190", + "FM5_Op1_SSG_EG", "191", + "FM6_Op1_SSG_EG", "192", + "FM4_Op2_SSG_EG", "194", + "FM5_Op2_SSG_EG", "195", + "FM6_Op2_SSG_EG", "196", + "FM4_Op3_SSG_EG", "198", + "FM5_Op3_SSG_EG", "199", + "FM6_Op3_SSG_EG", "19A", + "FM4_Op4_SSG_EG", "19C", + "FM5_Op4_SSG_EG", "19D", + "FM6_Op4_SSG_EG", "19E", + "FM4_FNum1", "1A0", + "FM5_FNum1", "1A1", + "FM6_FNum1", "1A2", + "FM4_FNum2", "1A4", + "FM5_FNum2", "1A5", + "FM6_FNum2", "1A6", + "FM4_FB_ALG", "1B0", + "FM5_FB_ALG", "1B1", + "FM6_FB_ALG", "1B2", + "FM4_Pan_LFO", "1B4", + "FM5_Pan_LFO", "1B5", + "FM6_Pan_LFO", "1B6", + NULL +}; + +const char** DivPlatformYM2610B::getRegisterSheet() { + return regCheatSheetYM2610B; +} + +const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xy: Setup LFO (x: enable; y: speed)"; + break; + case 0x11: + return "11xx: Set feedback (0 to 7)"; + break; + case 0x12: + return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; + break; + case 0x13: + return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; + break; + case 0x14: + return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; + break; + case 0x15: + return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; + break; + case 0x16: + return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; + break; + case 0x18: + return "18xx: Toggle extended channel 3 mode"; + break; + case 0x19: + return "19xx: Set attack of all operators (0 to 1F)"; + break; + case 0x1a: + return "1Axx: Set attack of operator 1 (0 to 1F)"; + break; + case 0x1b: + return "1Bxx: Set attack of operator 2 (0 to 1F)"; + break; + case 0x1c: + return "1Cxx: Set attack of operator 3 (0 to 1F)"; + break; + case 0x1d: + return "1Dxx: Set attack of operator 4 (0 to 1F)"; + break; + case 0x20: + return "20xx: Set SSG channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; + break; + case 0x21: + return "21xx: Set SSG noise frequency (0 to 1F)"; + break; + case 0x22: + return "22xy: Set SSG envelope mode (x: shape, y: enable for this channel)"; + break; + case 0x23: + return "23xx: Set SSG envelope period low byte"; + break; + case 0x24: + return "24xx: Set SSG envelope period high byte"; + break; + case 0x25: + return "25xx: SSG envelope slide up"; + break; + case 0x26: + return "26xx: SSG envelope slide down"; + break; + case 0x29: + return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; + break; + case 0x30: + return "30xx: Toggle hard envelope reset on new notes"; + break; + case 0x50: + return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)"; + break; + case 0x51: + return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)"; + break; + case 0x52: + return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)"; + break; + case 0x53: + return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)"; + break; + case 0x54: + return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)"; + break; + case 0x55: + return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)"; + break; + case 0x56: + return "56xx: Set decay of all operators (0 to 1F)"; + break; + case 0x57: + return "57xx: Set decay of operator 1 (0 to 1F)"; + break; + case 0x58: + return "58xx: Set decay of operator 2 (0 to 1F)"; + break; + case 0x59: + return "59xx: Set decay of operator 3 (0 to 1F)"; + break; + case 0x5a: + return "5Axx: Set decay of operator 4 (0 to 1F)"; + break; + case 0x5b: + return "5Bxx: Set decay 2 of all operators (0 to 1F)"; + break; + case 0x5c: + return "5Cxx: Set decay 2 of operator 1 (0 to 1F)"; + break; + case 0x5d: + return "5Dxx: Set decay 2 of operator 2 (0 to 1F)"; + break; + case 0x5e: + return "5Exx: Set decay 2 of operator 3 (0 to 1F)"; + break; + case 0x5f: + return "5Fxx: Set decay 2 of operator 4 (0 to 1F)"; + break; + } + 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_FNUM_BLOCK(note,11); +} + +double DivPlatformYM2610B::NOTE_ADPCMB(int note) { + if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + return parent->calcBaseFreq((double)chipClock/144,off,note,false); + } + return 0; +} + +void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { + static int os[2]; + + ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); + ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); + ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + + ymfm::ssg_engine::output_data ssgOut; + + ymfm::fm_channel>* fmChan[6]; + ymfm::adpcm_a_channel* adpcmAChan[6]; + for (int i=0; i<6; i++) { + fmChan[i]=fme->debug_channel(i); + adpcmAChan[i]=aae->debug_channel(i); + } + + for (size_t h=start; hwrite(0x0+((w.addr>>8)<<1),w.addr); + fm->write(0x1+((w.addr>>8)<<1),w.val); + regPool[w.addr&0x1ff]=w.val; + writes.pop(); + delay=4; + } + } + + fm->generate(&fmout); + + os[0]=fmout.data[0]+(fmout.data[2]>>1); + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + os[1]=fmout.data[1]+(fmout.data[2]>>1); + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + bufL[h]=os[0]; + bufR[h]=os[1]; + + + for (int i=0; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + } + + ssge->get_last_out(ssgOut); + for (int i=6; i<9; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]; + } + + for (int i=9; i<15; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1); + } + + oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + } +} + +void DivPlatformYM2610B::tick(bool sysTick) { + // PSG + ay->tick(sysTick); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); + + // FM + for (int i=0; i<6; i++) { + if (i==2 && extMode) continue; + chan[i].std.next(); + + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11); + } else { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { + chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11); + chan[i].freqChanged=true; + } + } + + if (chan[i].std.panL.had) { + chan[i].pan=chan[i].std.panL.val&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].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1) { + chan[i].keyOn=true; + } + } + + if (chan[i].std.alg.had) { + chan[i].state.alg=chan[i].std.alg.val; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + } + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } + if (chan[i].std.fms.had) { + chan[i].state.fms=chan[i].std.fms.val; + 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].std.ams.had) { + chan[i].state.ams=chan[i].std.ams.val; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + if (m.am.had) { + op.am=m.am.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.ar.had) { + op.ar=m.ar.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dr.had) { + op.dr=m.dr.val; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.mult.had) { + op.mult=m.mult.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.rr.had) { + op.rr=m.rr.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.sl.had) { + op.sl=m.sl.val; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.tl.had) { + op.tl=127-m.tl.val; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (m.rs.had) { + op.rs=m.rs.val; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.dt.had) { + op.dt=m.dt.val; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.d2r.had) { + op.d2r=m.d2r.val; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + + if (chan[i].keyOn || chan[i].keyOff) { + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + immWrite(baseAddr+ADDR_SL_RR,0x0f); + immWrite(baseAddr+ADDR_TL,0x7f); + oldWrites[baseAddr+ADDR_SL_RR]=-1; + oldWrites[baseAddr+ADDR_TL]=-1; + //rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } + immWrite(0x28,0x00|konOffs[i]); + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + for (int k=0; k<100; k++) { + immWrite(baseAddr+ADDR_SL_RR,0x0f); + } + } + } + chan[i].keyOff=false; + } + } + // ADPCM-B + if (chan[15].furnacePCM) { + chan[15].std.next(); + + if (chan[15].std.vol.had) { + chan[15].outVol=(chan[15].vol*MIN(64,chan[15].std.vol.val))/64; + immWrite(0x1b,chan[15].outVol); + } + + if (chan[15].std.arp.had) { + if (!chan[15].inPorta) { + if (chan[15].std.arp.mode) { + chan[15].baseFreq=NOTE_ADPCMB(chan[15].std.arp.val); + } else { + chan[15].baseFreq=NOTE_ADPCMB(chan[15].note+(signed char)chan[15].std.arp.val); + } + } + chan[15].freqChanged=true; + } else { + if (chan[15].std.arp.mode && chan[15].std.arp.finished) { + chan[15].baseFreq=NOTE_ADPCMB(chan[15].note); + chan[15].freqChanged=true; + } + } + } + if (chan[15].freqChanged) { + if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + chan[15].freq=parent->calcFreq(chan[15].baseFreq,chan[15].pitch,false,4,chan[15].pitch2,(double)chipClock/144,off); + } else { + chan[15].freq=0; + } + immWrite(0x19,chan[15].freq&0xff); + immWrite(0x1a,(chan[15].freq>>8)&0xff); + chan[15].freqChanged=false; + } + + for (int i=16; i<512; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } + + for (int i=0; i<6; i++) { + if (i==2 && extMode) continue; + if (chan[i].freqChanged) { + if (parent->song.linearPitch==2) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,4,chan[i].pitch2,chipClock,CHIP_FREQBASE,11); + } else { + int fNum=parent->calcFreq(chan[i].baseFreq&0x7ff,chan[i].pitch,false,4,chan[i].pitch2); + int block=(chan[i].baseFreq&0xf800)>>11; + if (fNum<0) fNum=0; + if (fNum>2047) { + while (block<7) { + fNum>>=1; + block++; + } + if (fNum>2047) fNum=2047; + } + chan[i].freq=(block<<11)|fNum; + } + if (chan[i].freq>0x3fff) chan[i].freq=0x3fff; + immWrite(chanOffs[i]+ADDR_FREQH,chan[i].freq>>8); + immWrite(chanOffs[i]+ADDR_FREQ,chan[i].freq&0xff); + chan[i].freqChanged=false; + } + if (chan[i].keyOn) { + immWrite(0x28,0xf0|konOffs[i]); + chan[i].keyOn=false; + } + } +} + +int DivPlatformYM2610B::dispatch(DivCommand c) { + if (c.chan>5 && c.chan<9) { + c.chan-=6; + return ay->dispatch(c); + } + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + if (c.chan>14) { // ADPCM-B + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + 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].macroInit(ins); + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + immWrite(0x1b,chan[c.chan].outVol); + } + 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; + } + } else { + chan[c.chan].sample=-1; + chan[c.chan].macroInit(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + immWrite(0x10,0x01); // reset + immWrite(0x12,0); + immWrite(0x13,0); + immWrite(0x14,0); + immWrite(0x15,0); + break; + } + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + 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 + chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); + chan[c.chan].freqChanged=true; + } + break; + } + if (c.chan>8) { // ADPCM-A + if (skipRegisterWrites) break; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + immWrite(0x110+c.chan-9,0); + immWrite(0x118+c.chan-9,0); + immWrite(0x120+c.chan-9,0); + immWrite(0x128+c.chan-9,0); + break; + } + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(0x110+c.chan-9,(s->offA>>8)&0xff); + immWrite(0x118+c.chan-9,s->offA>>16); + int end=s->offA+s->lengthA-1; + immWrite(0x120+c.chan-9,(end>>8)&0xff); + immWrite(0x128+c.chan-9,end>>16); + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + immWrite(0x100,0x00|(1<<(c.chan-9))); + break; + } + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + chan[c.chan].macroInit(ins); + if (c.chan<6) { + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + } + + if (chan[c.chan].insChanged) { + chan[c.chan].state=ins->fm; + } + + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + if (chan[c.chan].insChanged) { + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + 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)); + } + chan[c.chan].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FNUM_BLOCK(c.value,11); + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].keyOn=true; + chan[c.chan].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + if (c.chan>14) { + immWrite(0x10,0x01); // reset + break; + } + if (c.chan>8) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + break; + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + if (c.chan>14) { + immWrite(0x10,0x01); // reset + break; + } + if (c.chan>8) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + break; + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + } + if (c.chan>14) { // ADPCM-B + immWrite(0x1b,chan[c.chan].outVol); + break; + } + if (c.chan>8) { // ADPCM-A + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + break; + } + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + break; + } + case DIV_CMD_GET_VOLUME: { + return chan[c.chan].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].insChanged=true; + } + chan[c.chan].ins=c.value; + break; + case DIV_CMD_PANNING: { + if (c.value==0 && c.value2==0) { + chan[c.chan].pan=3; + } else { + chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); + } + if (c.chan>14) { + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + break; + } + if (c.chan>8) { + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + break; + } + 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; + } + case DIV_CMD_PITCH: { + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + if (c.chan>5 || parent->song.linearPitch==2) { // 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; + 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; + } + int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); + int boundaryTop=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,12,false); + int destFreq=NOTE_FNUM_BLOCK(c.value2,11); + int newFreq; + bool return2=false; + if (chan[c.chan].portaPause) { + chan[c.chan].baseFreq=chan[c.chan].portaPauseFreq; + } + if (destFreq>chan[c.chan].baseFreq) { + newFreq=chan[c.chan].baseFreq+c.value; + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=chan[c.chan].baseFreq-c.value; + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + // check for octave boundary + // what the heck! + if (!chan[c.chan].portaPause) { + if ((newFreq&0x7ff)>boundaryTop && (newFreq&0xf800)<0x3800) { + chan[c.chan].portaPauseFreq=(boundaryBottom)|((newFreq+0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + if ((newFreq&0x7ff)0) { + chan[c.chan].portaPauseFreq=newFreq=(boundaryTop-1)|((newFreq-0x800)&0xf800); + chan[c.chan].portaPause=true; + break; + } + } + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + chan[c.chan].baseFreq=newFreq; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + iface.sampleBank=sampleBank; + break; + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_FM_LFO: { + rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + break; + } + case DIV_CMD_FM_FB: { + if (c.chan>5) break; + chan[c.chan].state.fb=c.value&7; + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + break; + } + case DIV_CMD_FM_MULT: { + if (c.chan>5) break; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + break; + } + case DIV_CMD_FM_TL: { + if (c.chan>5) break; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + break; + } + case DIV_CMD_FM_AR: { + if (c.chan>5) break; + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_RS: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rs=c.value2&3; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.am=c.value2&1; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_DR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dr=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + break; + } + case DIV_CMD_FM_SL: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.sl=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_RR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.rr=c.value2&15; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + break; + } + case DIV_CMD_FM_D2R: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.d2r=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + break; + } + case DIV_CMD_FM_DT: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.dt=c.value&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.dt=c.value2&7; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + break; + } + case DIV_CMD_FM_SSG: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } else if (c.value<4) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ssgEnv=8^(c.value2&15); + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + break; + } + case DIV_CMD_FM_HARD_RESET: + chan[c.chan].hardReset=c.value; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + if (c.chan>14) return 255; + if (c.chan>8) return 31; + if (c.chan>5) return 15; + return 127; + break; + case DIV_CMD_PRE_PORTA: + if (c.chan>5) { + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FM)); + } + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformYM2610B::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (ch>14) { // ADPCM-B + immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6)); + } + if (ch>8) { // ADPCM-A + immWrite(0x108+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); + return; + } + if (ch>5) { // PSG + ay->muteChannel(ch-6,mute); + return; + } + // FM + rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); +} + +void DivPlatformYM2610B::forceIns() { + for (int i=0; i<6; i++) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + 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; + } + } + for (int i=9; i<16; i++) { + chan[i].insChanged=true; + } + + ay->forceIns(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); +} + +void* DivPlatformYM2610B::getChanState(int ch) { + return &chan[ch]; +} + +DivDispatchOscBuffer* DivPlatformYM2610B::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformYM2610B::getRegisterPool() { + return regPool; +} + +int DivPlatformYM2610B::getRegisterPoolSize() { + return 512; +} + +void DivPlatformYM2610B::poke(unsigned int addr, unsigned short val) { + immWrite(addr,val); +} + +void DivPlatformYM2610B::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +void DivPlatformYM2610B::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,512); + if (dumpWrites) { + addWrite(0xffffffff,0); + } + fm->reset(); + for (int i=0; i<16; i++) { + chan[i]=DivPlatformYM2610B::Channel(); + chan[i].std.setEngine(parent); + } + for (int i=0; i<6; i++) { + chan[i].vol=0x7f; + chan[i].outVol=0x7f; + } + for (int i=6; i<9; i++) { + chan[i].vol=0x0f; + } + for (int i=9; i<15; i++) { + chan[i].vol=0x1f; + } + chan[15].vol=0xff; + + for (int i=0; i<512; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + sampleBank=0; + + delay=0; + + extMode=false; + + // LFO + immWrite(0x22,0x08); + + // PCM volume + immWrite(0x101,0x3f); // A + immWrite(0x1b,0xff); // B + + ay->reset(); + ay->getRegisterWrites().clear(); + ay->flushWrites(); +} + +bool DivPlatformYM2610B::isStereo() { + return true; +} + +bool DivPlatformYM2610B::keyOffAffectsArp(int ch) { + return (ch>5); +} + +void DivPlatformYM2610B::notifyInsChange(int ins) { + for (int i=0; i<16; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } + ay->notifyInsChange(ins); +} + +void DivPlatformYM2610B::notifyInsDeletion(void* ins) { + ay->notifyInsDeletion(ins); +} + +void DivPlatformYM2610B::setSkipRegisterWrites(bool value) { + DivDispatch::setSkipRegisterWrites(value); + ay->setSkipRegisterWrites(value); +} + +int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + DivPlatformYM2610Base::init(p, channels, sugRate, flags); + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<16; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + chipClock=8000000; + rate=chipClock/16; + for (int i=0; i<16; i++) { + oscBuf[i]->rate=rate; + } + fm=new ymfm::ym2610b(iface); + // YM2149, 2MHz + ay=new DivPlatformAY8910; + ay->init(p,3,sugRate,35); + ay->toggleRegisterDump(true); + reset(); + return 16; +} + +void DivPlatformYM2610B::quit() { + for (int i=0; i<16; i++) { + delete oscBuf[i]; + } + ay->quit(); + delete ay; + delete fm; + DivPlatformYM2610Base::quit(); +} + +DivPlatformYM2610B::~DivPlatformYM2610B() { +} diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h new file mode 100644 index 000000000..0de28f6fd --- /dev/null +++ b/src/engine/platform/ym2608.h @@ -0,0 +1,130 @@ +/** + * 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 _YM2608_H +#define _YM2608_H +#include "../dispatch.h" +#include "../macroInt.h" +#include +#include "sound/ymfm/ymfm_opn.h" + +#include "ym2610.h" + +class DivPlatformYM2608: public DivPlatformYM2610Base { + protected: + const unsigned short chanOffs[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 + }; + + struct Channel { + DivInstrumentFM state; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins; + unsigned char psgMode, autoEnvNum, autoEnvDen; + signed char konCycles; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; + int vol, outVol; + int sample; + unsigned char pan; + DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + portaPauseFreq(0), + note(0), + ins(-1), + psgMode(1), + autoEnvNum(0), + autoEnvDen(0), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + inPorta(false), + furnacePCM(false), + hardReset(false), + vol(0), + outVol(15), + sample(-1), + pan(3) {} + }; + Channel chan[16]; + DivDispatchOscBuffer* oscBuf[16]; + bool isMuted[16]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; + ymfm::ym2608* fm; + ymfm::ym2608::output_data fmout; + unsigned char regPool[512]; + unsigned char lastBusy; + + DivPlatformAY8910* ay; + unsigned char sampleBank; + + int delay; + + bool extMode; + + short oldWrites[512]; + short pendingWrites[512]; + + double NOTE_OPNB(int ch, int note); + double NOTE_ADPCMB(int note); + 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); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + void setSkipRegisterWrites(bool val); + 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(); + ~DivPlatformYM2608(); +}; +#endif diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index c16b7312d..934789890 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1259,7 +1259,7 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_OPN]=new DivSysDef( - "Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, false, 0, false, + "Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, false, 0x151, false, {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, {"F1", "F2", "F3", "S1", "S2", "S3"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE}, diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 0a995e521..53eddc1b2 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -530,6 +530,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; } break; + case DIV_SYSTEM_OPN: + w->writeC(5|baseAddr1); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: @@ -949,6 +954,18 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { howManyChips++; } break; + case DIV_SYSTEM_OPN: + if (!hasOPN) { + hasOPN=disCont[i].dispatch->chipClock; + willExport[i]=true; + writeDACSamples=true; + } else if (!(hasOPN&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasOPN|=0x40000000; + howManyChips++; + } + break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index edde54f8a..9ac2e4bd9 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -355,6 +355,21 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool } break; } + case DIV_SYSTEM_OPN: { + if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) { + copyOfFlags=(flags&0x80000000)|0; + } + if (ImGui::RadioButton("PAL (3.54MHz)",(flags&3)==1)) { + copyOfFlags=(flags&0x80000000)|1; + } + if (ImGui::RadioButton("Arcade (4MHz)",(flags&3)==2)) { + copyOfFlags=(flags&0x80000000)|2; + } + if (ImGui::RadioButton("PC-9801-26K? TODO: CONFIRM (3MHz)",(flags&3)==3)) { + copyOfFlags=(flags&0x80000000)|3; + } + break; + } case DIV_SYSTEM_GB: case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: From a58d49a8318d941a24c65a90b6eef988050523a6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 16:09:23 -0500 Subject: [PATCH 26/68] GUI: more mobile-specific changes --- CMakeLists.txt | 6 ++++++ src/gui/gui.cpp | 19 ++++++++++++++++++- src/gui/gui.h | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b415fb99d..2e9e9c6a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,7 +45,13 @@ option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendor option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF) set(DEPENDENCIES_INCLUDE_DIRS "") + +if (ANDROID) +set(DEPENDENCIES_DEFINES "IS_MOBILE") +else() set(DEPENDENCIES_DEFINES "") +endif() + set(DEPENDENCIES_COMPILE_OPTIONS "") set(DEPENDENCIES_LIBRARIES "") set(DEPENDENCIES_LIBRARY_DIRS "") diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index edcac7fc5..c8e90237e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -59,7 +59,7 @@ extern "C" { #define BACKUP_FUR "/backup.fur" #endif -#ifdef ANDROID +#ifdef IS_MOBILE #define MOBILE_UI_DEFAULT true #else #define MOBILE_UI_DEFAULT false @@ -2453,6 +2453,15 @@ bool FurnaceGUI::loop() { wantCaptureKeyboard=ImGui::GetIO().WantTextInput; + if (wantCaptureKeyboard!=oldWantCaptureKeyboard) { + oldWantCaptureKeyboard=wantCaptureKeyboard; + if (wantCaptureKeyboard) { + SDL_StartTextInput(); + } else { + SDL_StopTextInput(); + } + } + if (wantCaptureKeyboard) { WAKE_UP; } @@ -2775,9 +2784,11 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu("settings")) { +#ifndef IS_MOBILE if (ImGui::MenuItem("full screen",BIND_FOR(GUI_ACTION_FULLSCREEN),fullScreen)) { doAction(GUI_ACTION_FULLSCREEN); } +#endif if (ImGui::MenuItem("lock layout",NULL,lockLayout)) { lockLayout=!lockLayout; } @@ -3657,7 +3668,11 @@ bool FurnaceGUI::init() { tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); lockLayout=e->getConfBool("lockLayout",false); +#ifdef IS_MOBILE + fullScreen=true; +#else fullScreen=e->getConfBool("fullScreen",false); +#endif mobileUI=e->getConfBool("mobileUI",MOBILE_UI_DEFAULT); syncSettings(); @@ -3679,6 +3694,7 @@ bool FurnaceGUI::init() { #endif SDL_SetHint("SDL_HINT_VIDEO_ALLOW_SCREENSAVER","1"); + SDL_SetHint("SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH","1"); SDL_Init(SDL_INIT_VIDEO); @@ -3870,6 +3886,7 @@ FurnaceGUI::FurnaceGUI(): displayExporting(false), vgmExportLoop(true), wantCaptureKeyboard(false), + oldWantCaptureKeyboard(false), displayMacroMenu(false), displayNew(false), fullScreen(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index 64aa78c36..e02a065c7 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -769,7 +769,7 @@ class FurnaceGUI { String mmlString[17]; String mmlStringW; - bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, displayMacroMenu; + bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu; bool displayNew, fullScreen, preserveChanPos; bool willExport[32]; int vgmExportVersion; From 5f17aa0e0329ce6af1a4b8b9d017ab154deb4023 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 11 May 2022 19:10:12 -0500 Subject: [PATCH 27/68] documentation mistake --- papers/doc/7-systems/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index fe5af1aba..d87cfd204 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -17,6 +17,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Yamaha YM2612 standalone](ym2612.md) - [Yamaha YM2151 standalone](ym2151.md) - [SegaPCM](segapcm.md) +- [Capcom QSound](qsound.md) - [Atari 2600](tia.md) - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) From f5ef1ad472b3fb6dff0e929bfe6be59e4cffe3a3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 02:25:13 -0500 Subject: [PATCH 28/68] more documentation work --- papers/doc/7-systems/ym2203.md | 101 ++++++++++++++++++++++++++++++++ papers/doc/7-systems/ym2608.md | 101 ++++++++++++++++++++++++++++++++ papers/doc/7-systems/ym2610b.md | 6 +- 3 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 papers/doc/7-systems/ym2203.md create mode 100644 papers/doc/7-systems/ym2608.md diff --git a/papers/doc/7-systems/ym2203.md b/papers/doc/7-systems/ym2203.md new file mode 100644 index 000000000..18f051e8d --- /dev/null +++ b/papers/doc/7-systems/ym2203.md @@ -0,0 +1,101 @@ +# Yamaha YM2203 (OPN) + +a cost-reduced version of the YM2151 (OPM). +it only has 3 FM channels instead of 8 and removes the LFO. + +however it does contain an AY/SSG part which provides 3 channels of square wave with noise and envelope. + +this chip was used in the PC-88/PC-98 series of computers, and in some arcade boards. + +several variants of this chip were released as well, with more features. + +# effects + +- `11xx`: set feedback of channel. +- `12xx`: set operator 1 level. +- `13xx`: set operator 2 level. +- `14xx`: set operator 3 level. +- `15xx`: set operator 4 level. +- `16xy`: set multiplier of operator. + - `x` is the operator (1-4). + - `y` is the mutliplier. +- `18xx`: toggle extended channel 3 mode. + - 0 disables it and 1 enables it. + - only in extended channel 3 system. +- `19xx`: set attack of all operators. +- `1Axx`: set attack of operator 1. +- `1Bxx`: set attack of operator 2. +- `1Cxx`: set attack of operator 3. +- `1Dxx`: set attack of operator 4. +- `20xx`: set SSG channel mode. `xx` may be one of the following: + - `00`: square + - `01`: noise + - `02`: square and noise + - `03`: nothing (apparently) + - `04`: envelope and square + - `05`: envelope and noise + - `06`: envelope and square and noise + - `07`: nothing +- `21xx`: set noise frequency. `xx` is a value between 00 and 1F. +- `22xy`: set envelope mode. + - `x` sets the envelope shape, which may be one of the following: + - `0: \___` decay + - `4: /___` attack once + - `8: \\\\` saw + - `9: \___` decay + - `A: \/\/` inverse obelisco + - `B: \¯¯¯` decay once + - `C: ////` inverse saw + - `D: /¯¯¯` attack + - `E: /\/\` obelisco + - `F: /___` attack once + - if `y` is 1 then the envelope will affect this channel. +- `23xx`: set envelope period low byte. +- `24xx`: set envelope period high byte. +- `25xx`: slide envelope period up. +- `26xx`: slide envelope period down. +- `29xy`: enable SSG 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. +- `30xx`: enable envelope hard reset. + - this works by inserting a quick release and tiny delay before a new note. +- `50xy`: set AM of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` determines whether AM is on. +- `51xy` set SL of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `52xy` set RR of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `53xy` set DT of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value: + - 0: +0 + - 1: +1 + - 2: +2 + - 3: +3 + - 4: -0 + - 5: -3 + - 6: -2 + - 7: -1 +- `54xy` set RS of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `55xy` set SSG-EG of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value (0-8). + - values between 0 and 7 set SSG-EG. + - value 8 disables it. +- `56xx`: set DR of all operators. +- `57xx`: set DR of operator 1. +- `58xx`: set DR of operator 2. +- `59xx`: set DR of operator 3. +- `5Axx`: set DR of operator 4. +- `5Bxx`: set D2R/SR of all operators. +- `5Cxx`: set D2R/SR of operator 1. +- `5Dxx`: set D2R/SR of operator 2. +- `5Exx`: set D2R/SR of operator 3. +- `5Fxx`: set D2R/SR of operator 4. diff --git a/papers/doc/7-systems/ym2608.md b/papers/doc/7-systems/ym2608.md new file mode 100644 index 000000000..51720d1b0 --- /dev/null +++ b/papers/doc/7-systems/ym2608.md @@ -0,0 +1,101 @@ +# Yamaha YM2608 (OPNA) + +like YM2203, but with twice the FM channels, an ADPCM channel and built-in drums ("rhythm")! + +it was one of the available sound chips for the PC-98 series of computers. + +the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built-in drums have been replaced with 6 sample channels. + +# effects + +- `10xy`: set LFO parameters. + - `x` toggles the LFO. + - `y` sets its speed. +- `11xx`: set feedback of channel. +- `12xx`: set operator 1 level. +- `13xx`: set operator 2 level. +- `14xx`: set operator 3 level. +- `15xx`: set operator 4 level. +- `16xy`: set multiplier of operator. + - `x` is the operator (1-4). + - `y` is the mutliplier. +- `18xx`: toggle extended channel 3 mode. + - 0 disables it and 1 enables it. + - only in extended channel 3 system. +- `19xx`: set attack of all operators. +- `1Axx`: set attack of operator 1. +- `1Bxx`: set attack of operator 2. +- `1Cxx`: set attack of operator 3. +- `1Dxx`: set attack of operator 4. +- `20xx`: set SSG channel mode. `xx` may be one of the following: + - `00`: square + - `01`: noise + - `02`: square and noise + - `03`: nothing (apparently) + - `04`: envelope and square + - `05`: envelope and noise + - `06`: envelope and square and noise + - `07`: nothing +- `21xx`: set noise frequency. `xx` is a value between 00 and 1F. +- `22xy`: set envelope mode. + - `x` sets the envelope shape, which may be one of the following: + - `0: \___` decay + - `4: /___` attack once + - `8: \\\\` saw + - `9: \___` decay + - `A: \/\/` inverse obelisco + - `B: \¯¯¯` decay once + - `C: ////` inverse saw + - `D: /¯¯¯` attack + - `E: /\/\` obelisco + - `F: /___` attack once + - if `y` is 1 then the envelope will affect this channel. +- `23xx`: set envelope period low byte. +- `24xx`: set envelope period high byte. +- `25xx`: slide envelope period up. +- `26xx`: slide envelope period down. +- `29xy`: enable SSG 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. +- `30xx`: enable envelope hard reset. + - this works by inserting a quick release and tiny delay before a new note. +- `50xy`: set AM of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` determines whether AM is on. +- `51xy` set SL of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `52xy` set RR of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `53xy` set DT of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value: + - 0: +0 + - 1: +1 + - 2: +2 + - 3: +3 + - 4: -0 + - 5: -3 + - 6: -2 + - 7: -1 +- `54xy` set RS of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value. +- `55xy` set SSG-EG of operator. + - `x` is the operator (1-4). a value of 0 means "all operators". + - `y` is the value (0-8). + - values between 0 and 7 set SSG-EG. + - value 8 disables it. +- `56xx`: set DR of all operators. +- `57xx`: set DR of operator 1. +- `58xx`: set DR of operator 2. +- `59xx`: set DR of operator 3. +- `5Axx`: set DR of operator 4. +- `5Bxx`: set D2R/SR of all operators. +- `5Cxx`: set D2R/SR of operator 1. +- `5Dxx`: set D2R/SR of operator 2. +- `5Exx`: set D2R/SR of operator 3. +- `5Fxx`: set D2R/SR of operator 4. diff --git a/papers/doc/7-systems/ym2610b.md b/papers/doc/7-systems/ym2610b.md index 17f274559..4133681ff 100644 --- a/papers/doc/7-systems/ym2610b.md +++ b/papers/doc/7-systems/ym2610b.md @@ -16,9 +16,9 @@ it is backward compatible with the original chip. - `16xy`: set multiplier of operator. - `x` is the operator (1-4). - `y` is the mutliplier. -- `18xx`: toggle extended channel 2 mode. +- `18xx`: toggle extended channel 3 mode. - 0 disables it and 1 enables it. - - only in extended channel 2 system. + - only in extended channel 3 system. - `19xx`: set attack of all operators. - `1Axx`: set attack of operator 1. - `1Bxx`: set attack of operator 2. @@ -95,4 +95,4 @@ it is backward compatible with the original chip. - `5Cxx`: set D2R/SR of operator 1. - `5Dxx`: set D2R/SR of operator 2. - `5Exx`: set D2R/SR of operator 3. -- `5Fxx`: set D2R/SR of operator 4. \ No newline at end of file +- `5Fxx`: set D2R/SR of operator 4. From 4e2e542c7a2b935964ecffa33b02a2eee491c0f4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 02:25:59 -0500 Subject: [PATCH 29/68] OPN: implement muting and remove panning --- src/engine/platform/ym2203.cpp | 121 ++++++++++++++++----------------- src/engine/platform/ym2203.h | 4 +- 2 files changed, 60 insertions(+), 65 deletions(-) diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 83233e8f5..722dc51bb 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -51,7 +51,6 @@ const char* regCheatSheetYM2203[]={ "SSG_Control_Env", "00D", // FM (Common) "FM_Test", "021", - "FM_LFOFreq", "022", "ClockA1", "024", "ClockA2", "025", "ClockB", "026", @@ -157,9 +156,6 @@ const char* regCheatSheetYM2203[]={ "FM1_FB_ALG", "0B0", "FM2_FB_ALG", "0B1", "FM3_FB_ALG", "0B2", - "FM1_Pan_LFO", "0B4", - "FM2_Pan_LFO", "0B5", - "FM3_Pan_LFO", "0B6", NULL }; @@ -169,9 +165,6 @@ const char** DivPlatformYM2203::getRegisterSheet() { const char* DivPlatformYM2203::getEffectName(unsigned char effect) { switch (effect) { - case 0x10: - return "10xy: Setup LFO (x: enable; y: speed)"; - break; case 0x11: return "11xx: Set feedback (0 to 7)"; break; @@ -313,7 +306,7 @@ void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t l fm->write(0x1,w.val); regPool[w.addr&0xff]=w.val; writes.pop(); - delay=16; + delay=6; } } @@ -363,10 +356,14 @@ void DivPlatformYM2203::tick(bool sysTick) { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } } @@ -387,11 +384,6 @@ void DivPlatformYM2203::tick(bool sysTick) { } } - if (chan[i].std.panL.had) { - chan[i].pan=chan[i].std.panL.val&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].std.pitch.had) { if (chan[i].std.pitch.mode) { chan[i].pitch2+=chan[i].std.pitch.val; @@ -429,14 +421,6 @@ void DivPlatformYM2203::tick(bool sysTick) { chan[i].state.fb=chan[i].std.fb.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } - if (chan[i].std.fms.had) { - chan[i].state.fms=chan[i].std.fms.val; - 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].std.ams.had) { - chan[i].state.ams=chan[i].std.ams.val; - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); - } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -467,10 +451,14 @@ void DivPlatformYM2203::tick(bool sysTick) { } if (m.tl.had) { op.tl=127-m.tl.val; - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } if (m.rs.had) { @@ -574,13 +562,17 @@ int DivPlatformYM2203::dispatch(DivCommand c) { for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { - if (!chan[c.chan].active || chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); - } + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); } else { - if (chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } if (chan[c.chan].insChanged) { @@ -594,7 +586,6 @@ int DivPlatformYM2203::dispatch(DivCommand c) { } if (chan[c.chan].insChanged) { rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); - 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)); } chan[c.chan].insChanged=false; @@ -631,10 +622,14 @@ int DivPlatformYM2203::dispatch(DivCommand c) { for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isOutput[chan[c.chan].state.alg][i]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } } break; @@ -649,15 +644,6 @@ int DivPlatformYM2203::dispatch(DivCommand c) { } chan[c.chan].ins=c.value; break; - case DIV_CMD_PANNING: { - if (c.value==0 && c.value2==0) { - chan[c.chan].pan=3; - } else { - chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); - } - 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; - } case DIV_CMD_PITCH: { chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; @@ -736,10 +722,6 @@ int DivPlatformYM2203::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; } - case DIV_CMD_FM_LFO: { - rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); - break; - } case DIV_CMD_FM_FB: { if (c.chan>2) break; chan[c.chan].state.fb=c.value&7; @@ -759,10 +741,14 @@ int DivPlatformYM2203::dispatch(DivCommand c) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.tl=c.value2; - if (isOutput[chan[c.chan].state.alg][c.value]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } break; } @@ -944,8 +930,19 @@ void DivPlatformYM2203::muteChannel(int ch, bool mute) { ay->muteChannel(ch-3,mute); return; } - // FM - rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[ch]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[ch].state.op[j]; + if (isMuted[ch]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[ch].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[ch].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } } void DivPlatformYM2203::forceIns() { @@ -953,10 +950,14 @@ void DivPlatformYM2203::forceIns() { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } } rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); @@ -966,7 +967,6 @@ void DivPlatformYM2203::forceIns() { rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } 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; @@ -1039,9 +1039,6 @@ void DivPlatformYM2203::reset() { extMode=false; - // LFO - immWrite(0x22,0x08); - ay->reset(); ay->getRegisterWrites().clear(); ay->flushWrites(); diff --git a/src/engine/platform/ym2203.h b/src/engine/platform/ym2203.h index 1a6dad584..ab527eeaa 100644 --- a/src/engine/platform/ym2203.h +++ b/src/engine/platform/ym2203.h @@ -45,7 +45,6 @@ class DivPlatformYM2203: public DivDispatch { bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; int vol, outVol; int sample; - unsigned char pan; DivMacroInt std; void macroInit(DivInstrument* which) { std.init(which); @@ -75,8 +74,7 @@ class DivPlatformYM2203: public DivDispatch { hardReset(false), vol(0), outVol(15), - sample(-1), - pan(3) {} + sample(-1) {} }; Channel chan[6]; DivDispatchOscBuffer* oscBuf[6]; From cdf4c55f0f5aaa8d8a6be7fc4625bf635c4f26c3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 02:39:46 -0500 Subject: [PATCH 30/68] SCC: fix forceIns (finally) fixes #447 --- src/engine/platform/scc.cpp | 13 ++++++++++++- src/engine/platform/scc.h | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index 96ec2dfd2..88132b124 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -105,6 +105,11 @@ void DivPlatformSCC::acquire(short* bufL, short* bufR, size_t start, size_t len) void DivPlatformSCC::updateWave(int ch) { int dstCh=(!isPlus && ch>=4)?3:ch; + if (ch==3) { + lastUpdated34=3; + } else if (ch==4) { + lastUpdated34=4; + } for (int i=0; i<32; i++) { rWrite(dstCh*32+i,(unsigned char)chan[ch].ws.output[i]-128); } @@ -281,10 +286,15 @@ void DivPlatformSCC::forceIns() { for (int i=0; i<5; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; - if (chan[i].active) { + if (isPlus || i<3) { updateWave(i); } } + if (!isPlus) { + if (lastUpdated34>=3) { + updateWave(lastUpdated34); + } + } } void* DivPlatformSCC::getChanState(int ch) { @@ -318,6 +328,7 @@ void DivPlatformSCC::reset() { if (dumpWrites) { addWrite(0xffffffff,0); } + lastUpdated34=0; } bool DivPlatformSCC::isStereo() { diff --git a/src/engine/platform/scc.h b/src/engine/platform/scc.h index 58059532f..11d6521c7 100644 --- a/src/engine/platform/scc.h +++ b/src/engine/platform/scc.h @@ -57,6 +57,7 @@ class DivPlatformSCC: public DivDispatch { DivDispatchOscBuffer* oscBuf[5]; bool isMuted[5]; unsigned char writeOscBuf; + int lastUpdated34; scc_core* scc; bool isPlus; From 0e03bf6bec5a1dafcac82db9401ebda0d34f7550 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 02:49:09 -0500 Subject: [PATCH 31/68] GUI: fix default vScroll for pitch macro --- src/gui/insEdit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 82b47fb69..cfe544582 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1152,8 +1152,8 @@ void FurnaceGUI::drawMacros(std::vector& macros) { i.macro->vZoom=24; i.macro->vScroll=120-12; } else if (i.macro->name=="pitch") { - i.macro->vZoom=64; - i.macro->vScroll=1024-32; + i.macro->vZoom=128; + i.macro->vScroll=2048-64; } else { i.macro->vZoom=i.max-i.min; i.macro->vScroll=0; From 76adccc0fc5a8cc1f2f254e0cea1a3a237e76653 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 03:01:35 -0500 Subject: [PATCH 32/68] GUI: even more improvements to macro editor --- src/gui/insEdit.cpp | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index cfe544582..567c0f3be 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1192,18 +1192,24 @@ void FurnaceGUI::drawMacros(std::vector& macros) { processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } if (i.macro->open) { - if (ImGui::IsItemHovered() && ctrlWheeling) { - if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { - i.macro->vZoom+=wheelY; - if (i.macro->vZoom<1) i.macro->vZoom=1; - if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min; - if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) { - i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + if (ImGui::IsItemHovered()) { + if (ctrlWheeling) { + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { + i.macro->vZoom+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vZoom<1) i.macro->vZoom=1; + if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min; + if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) { + i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } + } else { + macroPointSize+=wheelY; + if (macroPointSize<1) macroPointSize=1; + if (macroPointSize>256) macroPointSize=256; } - } else { - macroPointSize+=wheelY; - if (macroPointSize<1) macroPointSize=1; - if (macroPointSize>256) macroPointSize=256; + } else if ((ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) && wheelY!=0) { + i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; } } @@ -1214,12 +1220,16 @@ void FurnaceGUI::drawMacros(std::vector& macros) { ImS64 contentsV=(i.max-i.min); ImGui::SameLine(0.0f); + ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x); ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos()); - scrollbarPos.Min.x-=ImGui::GetStyle().ItemSpacing.x; scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize; - scrollbarPos.Max.x-=ImGui::GetStyle().ItemSpacing.x; scrollbarPos.Max.y+=i.height*dpiScale; ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale)); + if (ImGui::IsItemHovered() && ctrlWheeling) { + i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } if (ImGui::ScrollbarEx(scrollbarPos,ImGui::GetID("IMacroVScroll"),ImGuiAxis_Y,&scrollV,availV,contentsV,0)) { i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV; From 613b538fb9d13136a94cb1b744f32c85f7dac053 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 03:29:34 -0500 Subject: [PATCH 33/68] GUI: add setting to bring old macro vSlider back --- src/gui/gui.h | 2 ++ src/gui/insEdit.cpp | 47 ++++++++++++++++++++++++++++---------------- src/gui/settings.cpp | 10 +++++++++- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index e02a065c7..0501e2422 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -890,6 +890,7 @@ class FurnaceGUI { int hiddenSystems; int horizontalDataView; int noMultiSystem; + int oldMacroVSlider; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -974,6 +975,7 @@ class FurnaceGUI { hiddenSystems(0), horizontalDataView(0), noMultiSystem(0), + oldMacroVSlider(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 567c0f3be..0080f1da2 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1077,7 +1077,7 @@ void FurnaceGUI::drawMacros(std::vector& macros) { float loopIndicator[256]; int index=0; - float reservedSpace=ImGui::GetStyle().ScrollbarSize; + float reservedSpace=(settings.oldMacroVSlider)?(20.0f*dpiScale+ImGui::GetStyle().ItemSpacing.x):ImGui::GetStyle().ScrollbarSize; if (ImGui::BeginTable("MacroSpace",2)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); @@ -1215,24 +1215,37 @@ void FurnaceGUI::drawMacros(std::vector& macros) { // slider if (!i.isBitfield) { - ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll; - ImS64 availV=i.macro->vZoom; - ImS64 contentsV=(i.max-i.min); + if (settings.oldMacroVSlider) { + ImGui::SameLine(0.0f); + if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) { + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } + if (ImGui::IsItemHovered() && ctrlWheeling) { + i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } + } else { + ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll; + ImS64 availV=i.macro->vZoom; + ImS64 contentsV=(i.max-i.min); - ImGui::SameLine(0.0f); - ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x); - ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos()); - scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize; - scrollbarPos.Max.y+=i.height*dpiScale; - ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale)); - if (ImGui::IsItemHovered() && ctrlWheeling) { - i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); - if (i.macro->vScroll<0) i.macro->vScroll=0; - if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; - } + ImGui::SameLine(0.0f); + ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x); + ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos()); + scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize; + scrollbarPos.Max.y+=i.height*dpiScale; + ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale)); + if (ImGui::IsItemHovered() && ctrlWheeling) { + i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } - if (ImGui::ScrollbarEx(scrollbarPos,ImGui::GetID("IMacroVScroll"),ImGuiAxis_Y,&scrollV,availV,contentsV,0)) { - i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV; + if (ImGui::ScrollbarEx(scrollbarPos,ImGui::GetID("IMacroVScroll"),ImGuiAxis_Y,&scrollV,availV,contentsV,0)) { + i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV; + } } } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 1c22755a8..e6ba74a29 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1147,6 +1147,11 @@ void FurnaceGUI::drawSettings() { settings.sampleLayout=sampleLayoutB; } + bool oldMacroVSliderB=settings.oldMacroVSlider; + if (ImGui::Checkbox("Use classic macro editor vertical slider",&oldMacroVSliderB)) { + settings.oldMacroVSlider=oldMacroVSliderB; + } + bool roundedWindowsB=settings.roundedWindows; if (ImGui::Checkbox("Rounded window corners",&roundedWindowsB)) { settings.roundedWindows=roundedWindowsB; @@ -1849,6 +1854,7 @@ void FurnaceGUI::syncSettings() { settings.hiddenSystems=e->getConfInt("hiddenSystems",0); settings.horizontalDataView=e->getConfInt("horizontalDataView",0); settings.noMultiSystem=e->getConfInt("noMultiSystem",0); + settings.oldMacroVSlider=e->getConfInt("oldMacroVSlider",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1920,7 +1926,8 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.moveWindowTitle,0,1); clampSetting(settings.hiddenSystems,0,1); clampSetting(settings.horizontalDataView,0,1); - clampSetting(settings.noMultiSystem,0,1) + clampSetting(settings.noMultiSystem,0,1); + clampSetting(settings.oldMacroVSlider,0,1); settings.initialSys=e->decodeSysDesc(e->getConfString("initialSys","")); if (settings.initialSys.size()<4) { @@ -2034,6 +2041,7 @@ void FurnaceGUI::commitSettings() { e->setConf("initialSys",e->encodeSysDesc(settings.initialSys)); e->setConf("horizontalDataView",settings.horizontalDataView); e->setConf("noMultiSystem",settings.noMultiSystem); + e->setConf("oldMacroVSlider",settings.oldMacroVSlider); // colors for (int i=0; i Date: Wed, 11 May 2022 23:39:36 +0200 Subject: [PATCH 34/68] Add emulation settings for YRW801, TG100 and MU5 sample ROMs. --- src/engine/engine.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++ src/engine/engine.h | 14 +++++++- src/gui/gui.cpp | 27 +++++++++++++++ src/gui/gui.h | 13 ++++++-- src/gui/settings.cpp | 43 ++++++++++++++++++++++++ 5 files changed, 170 insertions(+), 3 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 364b0d135..157aea7fe 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -491,6 +491,77 @@ void DivEngine::notifyWaveChange(int wave) { BUSY_END; } +int DivEngine::loadSampleRom(String path, ssize_t expectedSize, unsigned char*& ret) { + ret = NULL; + if (path.empty()) { + return 0; + } + logI("loading ROM %s...",path); + FILE* f=ps_fopen(path.c_str(),"rb"); + if (f==NULL) { + logE("error: %s",strerror(errno)); + lastError=strerror(errno); + return -1; + } + if (fseek(f,0,SEEK_END)<0) { + logE("size error: %s",strerror(errno)); + lastError=fmt::sprintf("on seek: %s",strerror(errno)); + fclose(f); + return -1; + } + ssize_t len=ftell(f); + if (len==(SIZE_MAX>>1)) { + logE("could not get file length: %s",strerror(errno)); + lastError=fmt::sprintf("on pre tell: %s",strerror(errno)); + fclose(f); + return -1; + } + if (len<1) { + if (len==0) { + logE("that file is empty!"); + lastError="file is empty"; + } else { + logE("tell error: %s",strerror(errno)); + lastError=fmt::sprintf("on tell: %s",strerror(errno)); + } + fclose(f); + return -1; + } + if (len!=expectedSize) { + logE("ROM size mismatch, expected: %d bytes, was: %d bytes", expectedSize, len); + lastError=fmt::sprintf("ROM size mismatch, expected: %d bytes, was: %d", expectedSize, len); + return -1; + } + if (fseek(f,0,SEEK_SET)<0) { + logE("size error: %s",strerror(errno)); + lastError=fmt::sprintf("on get size: %s",strerror(errno)); + fclose(f); + return -1; + } + unsigned char* file=new unsigned char[len]; + if (fread(file,1,(size_t)len,f)!=(size_t)len) { + logE("read error: %s",strerror(errno)); + lastError=fmt::sprintf("on read: %s",strerror(errno)); + fclose(f); + delete[] file; + return -1; + } + fclose(f); + ret = file; + return 0; +} + +int DivEngine::loadSampleRoms() { + delete[] yrw801Rom; + delete[] tg100Rom; + delete[] mu5Rom; + int error = 0; + error += loadSampleRom(getConfString("yrw801Path",""), 0x200000, yrw801Rom); + error += loadSampleRom(getConfString("tg100Path",""), 0x200000, tg100Rom); + error += loadSampleRom(getConfString("mu5Path",""), 0x200000, mu5Rom); + return error; +} + void DivEngine::renderSamplesP() { BUSY_BEGIN; renderSamples(); @@ -2662,6 +2733,8 @@ bool DivEngine::init() { loadConf(); + loadSampleRoms(); + // set default system preset if (!hasLoadedSomething) { logD("setting default preset"); @@ -2735,5 +2808,8 @@ bool DivEngine::quit() { active=false; delete[] oscBuf[0]; delete[] oscBuf[1]; + delete[] yrw801Rom; + delete[] tg100Rom; + delete[] mu5Rom; return true; } diff --git a/src/engine/engine.h b/src/engine/engine.h index e9a5db2e0..701fc70fd 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -397,6 +397,8 @@ class DivEngine { void loadOPM(SafeReader& reader, std::vector& ret, String& stripPath); void loadFF(SafeReader& reader, std::vector& ret, String& stripPath); + int loadSampleRom(String path, ssize_t expectedSize, unsigned char*& ret); + bool initAudioBackend(); bool deinitAudioBackend(); @@ -783,6 +785,9 @@ class DivEngine { // get register cheatsheet const char** getRegisterSheet(int sys); + // load sample ROMs + int loadSampleRoms(); + // UNSAFE render samples - only execute when locked void renderSamples(); @@ -853,6 +858,10 @@ class DivEngine { // terminate the engine. bool quit(); + unsigned char* yrw801Rom; + unsigned char* tg100Rom; + unsigned char* mu5Rom; + DivEngine(): output(NULL), exportThread(NULL), @@ -927,7 +936,10 @@ class DivEngine { oscReadPos(0), oscWritePos(0), tickMult(1), - processTime(0) { + processTime(0), + yrw801Rom(NULL), + tg100Rom(NULL), + mu5Rom(NULL) { memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool)); memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool)); memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int)); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 700e55d09..17f04a9fd 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1501,6 +1501,19 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_YRW801_ROM_OPEN: + case GUI_FILE_TG100_ROM_OPEN: + case GUI_FILE_MU5_ROM_OPEN: + if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Load ROM", + {"compatible files", "*.rom *.bin", + "all files", ".*"}, + "compatible files{.rom,.bin},.*", + workingDirRom, + dpiScale + ); + break; } if (hasOpened) curFileDialog=type; //ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; @@ -2983,6 +2996,11 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_LAYOUT: workingDirLayout=fileDialog->getPath()+DIR_SEPARATOR_STR; break; + case GUI_FILE_YRW801_ROM_OPEN: + case GUI_FILE_TG100_ROM_OPEN: + case GUI_FILE_MU5_ROM_OPEN: + workingDirRom=fileDialog->getPath()+DIR_SEPARATOR_STR; + break; } if (fileDialog->accepted()) { fileName=fileDialog->getFileName(); @@ -3196,6 +3214,15 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_LAYOUT: exportLayout(copyOfName); break; + case GUI_FILE_YRW801_ROM_OPEN: + settings.yrw801Path=copyOfName; + break; + case GUI_FILE_TG100_ROM_OPEN: + settings.tg100Path=copyOfName; + break; + case GUI_FILE_MU5_ROM_OPEN: + settings.mu5Path=copyOfName; + break; } curFileDialog=GUI_FILE_OPEN; } diff --git a/src/gui/gui.h b/src/gui/gui.h index cfbf90e3f..7bf0095ea 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -257,7 +257,10 @@ enum FurnaceGUIFileDialogs { GUI_FILE_IMPORT_LAYOUT, GUI_FILE_EXPORT_COLORS, GUI_FILE_EXPORT_KEYBINDS, - GUI_FILE_EXPORT_LAYOUT + GUI_FILE_EXPORT_LAYOUT, + GUI_FILE_YRW801_ROM_OPEN, + GUI_FILE_TG100_ROM_OPEN, + GUI_FILE_MU5_ROM_OPEN }; enum FurnaceGUIWarnings { @@ -734,7 +737,7 @@ class FurnaceGUI { bool updateSampleTex; String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile; - String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout; + String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirRom; String mmlString[17]; String mmlStringW; @@ -792,6 +795,9 @@ class FurnaceGUI { int saaCore; int nesCore; int fdsCore; + String yrw801Path; + String tg100Path; + String mu5Path; int mainFont; int patFont; int audioRate; @@ -879,6 +885,9 @@ class FurnaceGUI { saaCore(1), nesCore(0), fdsCore(0), + yrw801Path(""), + tg100Path(""), + mu5Path(""), mainFont(0), patFont(0), audioRate(44100), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 87ed1fa1f..e139bfcde 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -887,6 +887,33 @@ void FurnaceGUI::drawSettings() { ImGui::Text("FDS core"); ImGui::SameLine(); ImGui::Combo("##FDSCore",&settings.fdsCore,nesCores,2); + + ImGui::Separator(); + ImGui::Text("Sample ROMs:"); + + ImGui::Text("OPL4 YRW801 path"); + ImGui::SameLine(); + ImGui::InputText("##YRW801Path",&settings.yrw801Path); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER "##YRW801Load")) { + openFileDialog(GUI_FILE_YRW801_ROM_OPEN); + } + + ImGui::Text("MultiPCM TG100 path"); + ImGui::SameLine(); + ImGui::InputText("##TG100Path",&settings.tg100Path); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER "##TG100Load")) { + openFileDialog(GUI_FILE_TG100_ROM_OPEN); + } + + ImGui::Text("MultiPCM MU5 path"); + ImGui::SameLine(); + ImGui::InputText("##MU5Path",&settings.mu5Path); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER "##MU5Load")) { + openFileDialog(GUI_FILE_MU5_ROM_OPEN); + } } ImGui::EndChild(); ImGui::EndTabItem(); @@ -1789,6 +1816,9 @@ void FurnaceGUI::syncSettings() { settings.saaCore=e->getConfInt("saaCore",1); settings.nesCore=e->getConfInt("nesCore",0); settings.fdsCore=e->getConfInt("fdsCore",0); + settings.yrw801Path=e->getConfString("yrw801Path",""); + settings.tg100Path=e->getConfString("tg100Path",""); + settings.mu5Path=e->getConfString("mu5Path",""); settings.mainFont=e->getConfInt("mainFont",0); settings.patFont=e->getConfInt("patFont",0); settings.mainFontPath=e->getConfString("mainFontPath",""); @@ -1960,6 +1990,10 @@ void FurnaceGUI::syncSettings() { } void FurnaceGUI::commitSettings() { + bool sampleRomsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") || + settings.tg100Path!=e->getConfString("tg100Path","") || + settings.mu5Path!=e->getConfString("mu5Path",""); + e->setConf("mainFontSize",settings.mainFontSize); e->setConf("patFontSize",settings.patFontSize); e->setConf("iconSize",settings.iconSize); @@ -1975,6 +2009,9 @@ void FurnaceGUI::commitSettings() { e->setConf("saaCore",settings.saaCore); e->setConf("nesCore",settings.nesCore); e->setConf("fdsCore",settings.fdsCore); + e->setConf("yrw801Path",settings.yrw801Path); + e->setConf("tg100Path",settings.tg100Path); + e->setConf("mu5Path",settings.mu5Path); e->setConf("mainFont",settings.mainFont); e->setConf("patFont",settings.patFont); e->setConf("mainFontPath",settings.mainFontPath); @@ -2063,6 +2100,12 @@ void FurnaceGUI::commitSettings() { e->saveConf(); + if (sampleRomsChanged) { + if (e->loadSampleRoms()) { + showError(e->getLastError()); + } + } + if (!e->switchMaster()) { showError("could not initialize audio!"); } From 3c3d75c60536219bb5d58a3e9175691ccbe4b24e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 14:21:58 -0500 Subject: [PATCH 35/68] add SCC documentation --- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/scc.md | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 papers/doc/7-systems/scc.md diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index d87cfd204..fcf2e8860 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -29,6 +29,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Yamaha OPL](opl.md) - [PC Speaker](pcspkr.md) - [Commodore PET](pet.md) +- [Konami SCC](scc.md) - [Commodore VIC-20](vic20.md) - [Konami VRC6](vrc6.md) - [Famicom Disk System](fds.md) diff --git a/papers/doc/7-systems/scc.md b/papers/doc/7-systems/scc.md new file mode 100644 index 000000000..fb69a9cb8 --- /dev/null +++ b/papers/doc/7-systems/scc.md @@ -0,0 +1,11 @@ +# Konami SCC/SCC+ + +the Sound Creative Chip (SCC) adds 5 channels of wavetable to your MSX! +it was used in (of course) several Konami games, which had better audio quality due to the extra channels provided by this chip (poor AY since nobody used the envelope for bass). + +the only problem? the waveform of the fourth channel is shared with the fifth one due to not enough memory in the chip! +the SCC+ fixes this issue though (while being compatible with SCC games). + +# effects + +- `10xx`: change wave. From 7d7740d96f9082928572d4f8c13bde6e86a8d0cd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 22:08:25 -0500 Subject: [PATCH 36/68] ... #452 cherry-picking --- papers/doc/7-systems/ym2203.md | 4 ++-- papers/doc/7-systems/ym2608.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/papers/doc/7-systems/ym2203.md b/papers/doc/7-systems/ym2203.md index 18f051e8d..6b0bbf8d9 100644 --- a/papers/doc/7-systems/ym2203.md +++ b/papers/doc/7-systems/ym2203.md @@ -1,11 +1,11 @@ # Yamaha YM2203 (OPN) a cost-reduced version of the YM2151 (OPM). -it only has 3 FM channels instead of 8 and removes the LFO. +it only has 3 FM channels instead of 8 and removes stereo, the LFO and DT2 (coarse detune). however it does contain an AY/SSG part which provides 3 channels of square wave with noise and envelope. -this chip was used in the PC-88/PC-98 series of computers, and in some arcade boards. +this chip was used in the NEC PC-88/PC-98 series of computers, the Fujitsu FM-7AV and in some arcade boards. several variants of this chip were released as well, with more features. diff --git a/papers/doc/7-systems/ym2608.md b/papers/doc/7-systems/ym2608.md index 51720d1b0..736258a38 100644 --- a/papers/doc/7-systems/ym2608.md +++ b/papers/doc/7-systems/ym2608.md @@ -1,8 +1,8 @@ # Yamaha YM2608 (OPNA) -like YM2203, but with twice the FM channels, an ADPCM channel and built-in drums ("rhythm")! +like YM2203, but with twice the FM channels, stereo, an ADPCM channel and built-in drums ("rhythm")! -it was one of the available sound chips for the PC-98 series of computers. +it was one of the available sound chips for the NEC PC-88VA and PC-98 series of computers. the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built-in drums have been replaced with 6 sample channels. From 1b4256bb2815b0a0e9b81b3deda6aaf08ad202da Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 12 May 2022 22:15:03 -0500 Subject: [PATCH 37/68] spelling change: Rom -> ROM - PLEASE READ @Grauw: please update your MultiPCM code. i know it's just a trivial change I made, but in my opinion "ROM" indicates it's an acronym... --- src/engine/engine.cpp | 33 +++++++++++++++++++++------------ src/engine/engine.h | 16 ++++++++-------- src/gui/gui.cpp | 4 ++-- src/gui/gui.h | 2 +- src/gui/settings.cpp | 6 +++--- 5 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index cc1428e15..99e41313c 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -491,7 +491,7 @@ void DivEngine::notifyWaveChange(int wave) { BUSY_END; } -int DivEngine::loadSampleRom(String path, ssize_t expectedSize, unsigned char*& ret) { +int DivEngine::loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret) { ret = NULL; if (path.empty()) { return 0; @@ -551,14 +551,23 @@ int DivEngine::loadSampleRom(String path, ssize_t expectedSize, unsigned char*& return 0; } -int DivEngine::loadSampleRoms() { - delete[] yrw801Rom; - delete[] tg100Rom; - delete[] mu5Rom; +int DivEngine::loadSampleROMs() { + if (yrw801ROM!=NULL) { + delete[] yrw801ROM; + yrw801ROM=NULL; + } + if (tg100ROM!=NULL) { + delete[] tg100ROM; + tg100ROM=NULL; + } + if (mu5ROM!=NULL) { + delete[] mu5ROM; + mu5ROM=NULL; + } int error = 0; - error += loadSampleRom(getConfString("yrw801Path",""), 0x200000, yrw801Rom); - error += loadSampleRom(getConfString("tg100Path",""), 0x200000, tg100Rom); - error += loadSampleRom(getConfString("mu5Path",""), 0x200000, mu5Rom); + error += loadSampleROM(getConfString("yrw801Path",""), 0x200000, yrw801ROM); + error += loadSampleROM(getConfString("tg100Path",""), 0x200000, tg100ROM); + error += loadSampleROM(getConfString("mu5Path",""), 0x200000, mu5ROM); return error; } @@ -2779,7 +2788,7 @@ bool DivEngine::init() { loadConf(); - loadSampleRoms(); + loadSampleROMs(); // set default system preset if (!hasLoadedSomething) { @@ -2854,8 +2863,8 @@ bool DivEngine::quit() { active=false; delete[] oscBuf[0]; delete[] oscBuf[1]; - delete[] yrw801Rom; - delete[] tg100Rom; - delete[] mu5Rom; + if (yrw801ROM!=NULL) delete[] yrw801ROM; + if (tg100ROM!=NULL) delete[] tg100ROM; + if (mu5ROM!=NULL) delete[] mu5ROM; return true; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 3338b4a54..8a95e935f 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -397,7 +397,7 @@ class DivEngine { void loadOPM(SafeReader& reader, std::vector& ret, String& stripPath); void loadFF(SafeReader& reader, std::vector& ret, String& stripPath); - int loadSampleRom(String path, ssize_t expectedSize, unsigned char*& ret); + int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret); bool initAudioBackend(); bool deinitAudioBackend(); @@ -786,7 +786,7 @@ class DivEngine { const char** getRegisterSheet(int sys); // load sample ROMs - int loadSampleRoms(); + int loadSampleROMs(); // UNSAFE render samples - only execute when locked void renderSamples(); @@ -861,9 +861,9 @@ class DivEngine { // terminate the engine. bool quit(); - unsigned char* yrw801Rom; - unsigned char* tg100Rom; - unsigned char* mu5Rom; + unsigned char* yrw801ROM; + unsigned char* tg100ROM; + unsigned char* mu5ROM; DivEngine(): output(NULL), @@ -940,9 +940,9 @@ class DivEngine { oscWritePos(0), tickMult(1), processTime(0), - yrw801Rom(NULL), - tg100Rom(NULL), - mu5Rom(NULL) { + yrw801ROM(NULL), + tg100ROM(NULL), + mu5ROM(NULL) { memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool)); memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool)); memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int)); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4d237c1d5..abeb569d4 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1516,7 +1516,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { {"compatible files", "*.rom *.bin", "all files", ".*"}, "compatible files{.rom,.bin},.*", - workingDirRom, + workingDirROM, dpiScale ); break; @@ -3040,7 +3040,7 @@ bool FurnaceGUI::loop() { case GUI_FILE_YRW801_ROM_OPEN: case GUI_FILE_TG100_ROM_OPEN: case GUI_FILE_MU5_ROM_OPEN: - workingDirRom=fileDialog->getPath()+DIR_SEPARATOR_STR; + workingDirROM=fileDialog->getPath()+DIR_SEPARATOR_STR; break; } if (fileDialog->accepted()) { diff --git a/src/gui/gui.h b/src/gui/gui.h index 03a3d7e32..61b2d912f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -768,7 +768,7 @@ class FurnaceGUI { bool updateSampleTex; String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile; - String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirRom; + String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout, workingDirROM; String mmlString[17]; String mmlStringW; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index c38e1dc89..f44c2aa92 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1990,7 +1990,7 @@ void FurnaceGUI::syncSettings() { } void FurnaceGUI::commitSettings() { - bool sampleRomsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") || + bool sampleROMsChanged = settings.yrw801Path!=e->getConfString("yrw801Path","") || settings.tg100Path!=e->getConfString("tg100Path","") || settings.mu5Path!=e->getConfString("mu5Path",""); @@ -2100,8 +2100,8 @@ void FurnaceGUI::commitSettings() { e->saveConf(); - if (sampleRomsChanged) { - if (e->loadSampleRoms()) { + if (sampleROMsChanged) { + if (e->loadSampleROMs()) { showError(e->getLastError()); } } From f2db15b2ce6720e054eeca0a7828cbd40662f638 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 02:52:43 -0500 Subject: [PATCH 38/68] add OPNA chip (work in progress) --- CMakeLists.txt | 3 + TODO.md | 4 +- src/engine/dispatchContainer.cpp | 4 + src/engine/platform/sound/rss.h | 543 ++++++++++++++++++++++++ src/engine/platform/ym2203.cpp | 2 + src/engine/platform/ym2608.cpp | 241 ++++++----- src/engine/platform/ym2608.h | 21 +- src/engine/platform/ym2608Interface.cpp | 42 ++ 8 files changed, 744 insertions(+), 116 deletions(-) create mode 100644 src/engine/platform/sound/rss.h create mode 100644 src/engine/platform/ym2608Interface.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e9e9c6a4..4f912faea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -318,6 +318,8 @@ src/engine/platform/sound/vrcvi/vrcvi.cpp src/engine/platform/sound/scc/scc.cpp +src/engine/platform/ym2608Interface.cpp + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -352,6 +354,7 @@ src/engine/platform/c64.cpp src/engine/platform/arcade.cpp src/engine/platform/tx81z.cpp src/engine/platform/ym2203.cpp +src/engine/platform/ym2608.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp src/engine/platform/ym2610b.cpp diff --git a/TODO.md b/TODO.md index fc526b29e..52eae1713 100644 --- a/TODO.md +++ b/TODO.md @@ -5,8 +5,6 @@ - input pad - settings - RF5C68 system -- OPN system -- OPNA system - ZX beeper system - Y8950 system - maybe YMU759 ADPCM channel @@ -27,4 +25,4 @@ - Apply button in settings - find and replace - finish wave synth -- add mono/poly note preview button \ No newline at end of file +- add mono/poly note preview button diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 7595d14bb..46efffc31 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -30,6 +30,7 @@ #include "platform/arcade.h" #include "platform/tx81z.h" #include "platform/ym2203.h" +#include "platform/ym2608.h" #include "platform/ym2610.h" #include "platform/ym2610ext.h" #include "platform/ym2610b.h" @@ -238,6 +239,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_OPN: dispatch=new DivPlatformYM2203; break; + case DIV_SYSTEM_PC98: + dispatch=new DivPlatformYM2608; + break; case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: diff --git a/src/engine/platform/sound/rss.h b/src/engine/platform/sound/rss.h new file mode 100644 index 000000000..1fcd6ada3 --- /dev/null +++ b/src/engine/platform/sound/rss.h @@ -0,0 +1,543 @@ +/* + This data is derived from the chip's output - internal ROM can't be read. + It was verified, using real YM2608, that this ADPCM stream produces 100% correct output signal. +*/ + +const unsigned char YM2608_ADPCM_ROM[0x2000] = { + +/* Source: 01BD.ROM */ +/* Length: 448 / 0x000001C0 */ + +0x88,0x08,0x08,0x08,0x00,0x88,0x16,0x76,0x99,0xB8,0x22,0x3A,0x84,0x3C,0xB1,0x54, +0x10,0xA9,0x98,0x32,0x80,0x33,0x9A,0xA7,0x4A,0xB4,0x58,0xBC,0x15,0x29,0x8A,0x97, +0x9B,0x44,0xAC,0x80,0x12,0xDE,0x13,0x1B,0xC0,0x58,0xC8,0x11,0x0A,0xA2,0x1A,0xA0, +0x00,0x98,0x0B,0x93,0x9E,0x92,0x0A,0x88,0xBE,0x14,0x1B,0x98,0x08,0xA1,0x4A,0xC1, +0x30,0xD9,0x33,0x98,0x10,0x89,0x17,0x1A,0x82,0x29,0x37,0x0C,0x83,0x50,0x9A,0x24, +0x1A,0x83,0x10,0x23,0x19,0xB3,0x72,0x8A,0x16,0x10,0x0A,0x93,0x70,0x99,0x23,0x99, +0x02,0x20,0x91,0x18,0x02,0x41,0xAB,0x24,0x18,0x81,0x99,0x4A,0xE8,0x28,0x9A,0x99, +0xA1,0x2F,0xA8,0x9D,0x90,0x08,0xCC,0xA3,0x1D,0xCA,0x82,0x0B,0xD8,0x08,0xB9,0x09, +0xBC,0xB8,0x00,0xBE,0x90,0x1B,0xCA,0x00,0x9B,0x8A,0xA8,0x91,0x0F,0xB3,0x3D,0xB8, +0x31,0x0B,0xA5,0x0A,0x11,0xA1,0x48,0x92,0x10,0x50,0x91,0x30,0x23,0x09,0x37,0x39, +0xA2,0x72,0x89,0x92,0x30,0x83,0x1C,0x96,0x28,0xB9,0x24,0x8C,0xA1,0x31,0xAD,0xA9, +0x13,0x9C,0xBA,0xA8,0x0B,0xBF,0xB8,0x9B,0xCA,0x88,0xDB,0xB8,0x19,0xFC,0x92,0x0A, +0xBA,0x89,0xAB,0xB8,0xAB,0xD8,0x08,0xAD,0xBA,0x33,0x9D,0xAA,0x83,0x3A,0xC0,0x40, +0xB9,0x15,0x39,0xA2,0x52,0x89,0x02,0x63,0x88,0x13,0x23,0x03,0x52,0x02,0x54,0x00, +0x11,0x23,0x23,0x35,0x20,0x01,0x44,0x41,0x80,0x24,0x40,0xA9,0x45,0x19,0x81,0x12, +0x81,0x02,0x11,0x21,0x19,0x02,0x61,0x8A,0x13,0x3A,0x10,0x12,0x23,0x8B,0x37,0x18, +0x91,0x24,0x10,0x81,0x34,0x20,0x05,0x32,0x82,0x53,0x20,0x14,0x33,0x31,0x34,0x52, +0x00,0x43,0x32,0x13,0x52,0x22,0x13,0x52,0x11,0x43,0x11,0x32,0x32,0x32,0x22,0x02, +0x13,0x12,0x89,0x22,0x19,0x81,0x81,0x08,0xA8,0x08,0x8B,0x90,0x1B,0xBA,0x8A,0x9B, +0xB9,0x89,0xCA,0xB9,0xAB,0xCA,0x9B,0xCA,0xB9,0xAB,0xDA,0x99,0xAC,0xBB,0x9B,0xAC, +0xAA,0xBA,0xAC,0xAB,0x9A,0xAA,0xAA,0xBA,0xB8,0xA9,0xBA,0x99,0xA9,0x9A,0xA0,0x8A, +0xA9,0x08,0x8A,0xA9,0x00,0x99,0x89,0x88,0x98,0x08,0x99,0x00,0x89,0x80,0x08,0x98, +0x00,0x88,0x88,0x80,0x90,0x80,0x90,0x80,0x81,0x99,0x08,0x88,0x99,0x09,0x00,0x1A, +0xA8,0x10,0x9A,0x88,0x08,0x0A,0x8A,0x89,0x99,0xA8,0x98,0xA9,0x99,0x99,0xA9,0x99, +0xAA,0x8A,0xAA,0x9B,0x8A,0x9A,0xA9,0x9A,0xBA,0x99,0x9A,0xAA,0x99,0x89,0xA9,0x99, +0x98,0x9A,0x98,0x88,0x09,0x89,0x09,0x08,0x08,0x09,0x18,0x18,0x00,0x12,0x00,0x11, +0x11,0x11,0x12,0x12,0x21,0x21,0x22,0x22,0x22,0x22,0x22,0x22,0x32,0x31,0x32,0x31, +0x32,0x32,0x21,0x31,0x21,0x32,0x21,0x12,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + +/* Source: 02SD.ROM */ +/* Length: 640 / 0x00000280 */ + +0x0A,0xDC,0x14,0x0B,0xBA,0xBC,0x01,0x0F,0xF5,0x2F,0x87,0x19,0xC9,0x24,0x1B,0xA1, +0x31,0x99,0x90,0x32,0x32,0xFE,0x83,0x48,0xA8,0xA9,0x23,0x19,0xBC,0x91,0x02,0x41, +0xDE,0x81,0x28,0xA8,0x0A,0xB1,0x72,0xDA,0x23,0xBC,0x04,0x19,0xB8,0x21,0x8A,0x03, +0x29,0xBA,0x14,0x21,0x0B,0xC0,0x43,0x08,0x91,0x50,0x93,0x0F,0x86,0x1A,0x91,0x18, +0x21,0xCB,0x27,0x0A,0xA1,0x42,0x8C,0xA9,0x21,0x10,0x08,0xAB,0x94,0x2A,0xDA,0x02, +0x8B,0x91,0x09,0x98,0xAE,0x80,0xA9,0x02,0x0A,0xE9,0x21,0xBB,0x15,0x20,0xBE,0x92, +0x42,0x09,0xA9,0x11,0x34,0x08,0x12,0x0A,0x27,0x29,0xA1,0x52,0x12,0x8E,0x92,0x28, +0x92,0x2B,0xD1,0x23,0xBF,0x81,0x10,0x99,0xA8,0x0A,0xC4,0x3B,0xB9,0xB0,0x00,0x62, +0xCF,0x92,0x29,0x92,0x2B,0xB1,0x1C,0xB2,0x72,0xAA,0x88,0x11,0x18,0x80,0x13,0x9E, +0x03,0x18,0xB0,0x60,0xA1,0x28,0x88,0x08,0x04,0x10,0x8F,0x96,0x19,0x90,0x01,0x09, +0xC8,0x50,0x91,0x8A,0x01,0xAB,0x03,0x50,0xBA,0x9D,0x93,0x68,0xBA,0x80,0x22,0xCB, +0x41,0xBC,0x92,0x60,0xB9,0x1A,0x95,0x4A,0xC8,0x20,0x88,0x33,0xAC,0x92,0x38,0x83, +0x09,0x80,0x16,0x09,0x29,0xD0,0x54,0x8C,0xA2,0x28,0x91,0x89,0x93,0x60,0xCD,0x85, +0x1B,0xA1,0x49,0x90,0x8A,0x80,0x34,0x0C,0xC9,0x14,0x19,0x98,0xA0,0x40,0xA9,0x21, +0xD9,0x34,0x0A,0xA9,0x10,0x23,0xCB,0x25,0xAA,0x25,0x9B,0x13,0xCD,0x16,0x09,0xA0, +0x80,0x01,0x19,0x90,0x88,0x21,0xAC,0x33,0x8B,0xD8,0x27,0x3B,0xB8,0x81,0x31,0x80, +0xAF,0x97,0x0A,0x82,0x0A,0xA0,0x21,0x89,0x8A,0xA2,0x32,0x8D,0xBB,0x87,0x19,0x21, +0xC9,0xBC,0x45,0x09,0x90,0x09,0xA1,0x24,0x1A,0xD0,0x10,0x08,0x11,0xA9,0x21,0xE8, +0x60,0xA9,0x14,0x0C,0xD1,0x32,0xAB,0x04,0x0C,0x81,0x90,0x29,0x83,0x9B,0x01,0x8F, +0x97,0x0B,0x82,0x18,0x88,0xBA,0x06,0x39,0xC8,0x23,0xBC,0x04,0x09,0x92,0x08,0x1A, +0xBB,0x74,0x8C,0x81,0x18,0x81,0x9D,0x83,0x41,0xCD,0x81,0x40,0x9A,0x90,0x10,0x12, +0x9C,0xA1,0x68,0xD8,0x33,0x9C,0x91,0x01,0x12,0xBE,0x02,0x09,0x12,0x99,0x9A,0x36, +0x0A,0xB0,0x30,0x88,0xA3,0x2D,0x12,0xBC,0x03,0x3A,0x11,0xBD,0x08,0xC8,0x62,0x80, +0x8B,0xD8,0x23,0x38,0xF9,0x12,0x08,0x99,0x91,0x21,0x99,0x85,0x2F,0xB2,0x30,0x90, +0x88,0xD9,0x53,0xAC,0x82,0x19,0x91,0x20,0xCC,0x96,0x29,0xC9,0x24,0x89,0x80,0x99, +0x12,0x08,0x18,0x88,0x99,0x23,0xAB,0x73,0xCB,0x33,0x9F,0x04,0x2B,0xB1,0x08,0x03, +0x1B,0xC9,0x21,0x32,0xFA,0x33,0xDB,0x02,0x33,0xAE,0xB9,0x54,0x8B,0xA1,0x20,0x89, +0x90,0x11,0x88,0x09,0x98,0x23,0xBE,0x37,0x8D,0x81,0x20,0xAA,0x34,0xBB,0x13,0x18, +0xB9,0x40,0xB1,0x18,0x83,0x8E,0xB2,0x72,0xBC,0x82,0x30,0xA9,0x9A,0x24,0x8B,0x27, +0x0E,0x91,0x20,0x90,0x08,0xB0,0x32,0xB9,0x21,0xB0,0xAC,0x45,0x9A,0xA1,0x50,0xA9, +0x80,0x0A,0x26,0x9B,0x11,0xBB,0x23,0x71,0xCB,0x12,0x10,0xB8,0x40,0xA9,0xA5,0x39, +0xC0,0x30,0xB2,0x20,0xAA,0xBA,0x76,0x1C,0xC1,0x48,0x98,0x80,0x18,0x81,0xAA,0x23, +0x9C,0xA2,0x32,0xAC,0x9A,0x43,0x9C,0x12,0xAD,0x82,0x72,0xBC,0x00,0x82,0x39,0xD1, +0x3A,0xB8,0x35,0x9B,0x10,0x40,0xF9,0x22,0x0A,0xC0,0x51,0xB9,0x82,0x18,0x98,0xA3, +0x79,0xD0,0x20,0x88,0x09,0x01,0x99,0x82,0x11,0x38,0xFC,0x33,0x09,0xC8,0x40,0xA9, +0x11,0x29,0xAA,0x94,0x3A,0xC2,0x4A,0xC0,0x89,0x52,0xBC,0x11,0x08,0x09,0xB8,0x71, +0xA9,0x08,0xA8,0x62,0x8D,0x92,0x10,0x00,0x9E,0x94,0x38,0xBA,0x13,0x88,0x90,0x4A, +0xE2,0x30,0xBA,0x02,0x00,0x19,0xD9,0x62,0xBB,0x04,0x0B,0xA3,0x68,0xB9,0x21,0x88, +0x9D,0x04,0x10,0x8C,0xC8,0x62,0x99,0xAA,0x24,0x1A,0x80,0x9A,0x14,0x9B,0x26,0x8C, +0x92,0x30,0xB9,0x09,0xA3,0x71,0xBB,0x10,0x19,0x82,0x39,0xDB,0x02,0x44,0x9F,0x10, + +/* Source: 04TOP.ROM */ +/* Length: 5952 / 0x00001740 */ + +0x07,0xFF,0x7C,0x3C,0x31,0xC6,0xC4,0xBB,0x7F,0x7F,0x7B,0x82,0x8A,0x4D,0x5F,0x7C, +0x3E,0x44,0xD2,0xB3,0xA0,0x19,0x1B,0x6C,0x81,0x28,0xC4,0xA1,0x1C,0x4B,0x18,0x00, +0x2A,0xA2,0x0A,0x7C,0x2A,0x00,0x01,0x89,0x98,0x48,0x8A,0x3C,0x28,0x2A,0x5B,0x3E, +0x3A,0x1A,0x3B,0x3D,0x4B,0x3B,0x4A,0x08,0x2A,0x1A,0x2C,0x4A,0x3B,0x82,0x99,0x3C, +0x5D,0x29,0x2B,0x39,0x0B,0x23,0xAB,0x1A,0x4C,0x79,0xA3,0x01,0xC1,0x2A,0x0A,0x38, +0xA7,0xB9,0x12,0x1F,0x29,0x08,0x82,0xA1,0x08,0xA9,0x42,0xAA,0x95,0xB3,0x90,0x81, +0x09,0xD4,0x1A,0x80,0x1B,0x07,0xB8,0x12,0x8E,0x49,0x81,0x92,0xD3,0x90,0xA1,0x2A, +0x02,0xE1,0xA3,0x99,0x02,0xB3,0x94,0xB3,0xB0,0xF4,0x98,0x93,0x90,0x13,0xE1,0x81, +0x99,0x38,0x91,0xA6,0xD3,0x99,0x94,0xC1,0x83,0xB1,0x92,0x98,0x49,0xC4,0xB2,0xA4, +0xA3,0xD0,0x1A,0x30,0xBA,0x59,0x02,0xD4,0xA0,0xA4,0xA2,0x8A,0x01,0x00,0xB7,0xA8, +0x18,0x2A,0x2B,0x1E,0x23,0xC8,0x1A,0x00,0x39,0xA0,0x18,0x92,0x4F,0x2D,0x5A,0x10, +0x89,0x81,0x2A,0x8B,0x6A,0x02,0x09,0xB3,0x8D,0x48,0x1B,0x80,0x19,0x34,0xF8,0x29, +0x0A,0x7B,0x2A,0x28,0x81,0x0C,0x02,0x1E,0x29,0x09,0x12,0xC2,0x94,0xE1,0x18,0x98, +0x02,0xC4,0x89,0x91,0x1A,0x20,0xA9,0x02,0x1B,0x48,0x8E,0x20,0x88,0x2D,0x08,0x59, +0x1B,0x02,0xA3,0xB1,0x8A,0x1E,0x58,0x80,0xC2,0xB6,0x88,0x91,0x88,0x11,0xA1,0xA3, +0xE2,0x01,0xB0,0x19,0x11,0x09,0xF4,0x88,0x09,0x88,0x19,0x89,0x12,0xF1,0x2A,0x28, +0x8C,0x25,0x99,0xA4,0x98,0x39,0xA1,0x00,0xD0,0x58,0xAA,0x59,0x01,0x0C,0x00,0x2B, +0x00,0x08,0x89,0x6B,0x69,0x90,0x01,0x90,0x98,0x12,0xB3,0xF3,0xA0,0x89,0x02,0x3B, +0x0C,0x50,0xA9,0x4E,0x6B,0x19,0x28,0x09,0xA2,0x08,0x2F,0x20,0x88,0x92,0x8A,0x11, +0xC4,0x93,0xF1,0x18,0x88,0x11,0xF2,0x80,0x92,0xA8,0x02,0xA8,0xB7,0xB3,0xA3,0xA0, +0x88,0x1A,0x40,0xE2,0x91,0x19,0x88,0x18,0x91,0x83,0xC1,0xB5,0x92,0xA9,0xC6,0x90, +0x01,0xC2,0x81,0x98,0x03,0xF0,0x00,0x2C,0x2A,0x92,0x2C,0x83,0x1F,0x3A,0x29,0x00, +0xB8,0x70,0xAB,0x69,0x18,0x89,0x10,0x0D,0x12,0x0B,0x88,0x4A,0x3A,0x9B,0x70,0xA8, +0x28,0x2F,0x2A,0x3A,0x1B,0x85,0x88,0x8B,0x6A,0x29,0x00,0x91,0x91,0x1B,0x7C,0x29, +0x01,0x88,0x90,0x19,0x2B,0x2B,0x00,0x39,0xA8,0x5E,0x21,0x89,0x91,0x09,0x3A,0x6F, +0x2A,0x18,0x18,0x8B,0x50,0x89,0x2B,0x19,0x49,0x88,0x29,0xF5,0x89,0x08,0x09,0x12, +0xAA,0x15,0xB0,0x82,0xAC,0x38,0x00,0x3F,0x81,0x10,0xB0,0x49,0xA2,0x81,0x3A,0xC8, +0x87,0x90,0xC4,0xA3,0x99,0x19,0x83,0xE1,0x84,0xE2,0xA2,0x90,0x80,0x93,0xB5,0xC4, +0xB3,0xA1,0x0A,0x18,0x92,0xC4,0xA0,0x93,0x0C,0x3A,0x18,0x01,0x1E,0x20,0xB1,0x82, +0x8C,0x03,0xB5,0x2E,0x82,0x19,0xB2,0x1B,0x1B,0x6B,0x4C,0x19,0x12,0x8B,0x5A,0x11, +0x0C,0x3A,0x2C,0x18,0x3D,0x08,0x2A,0x5C,0x18,0x00,0x88,0x3D,0x29,0x80,0x2A,0x09, +0x00,0x7A,0x0A,0x10,0x0B,0x69,0x98,0x10,0x81,0x3F,0x00,0x18,0x19,0x91,0xB7,0x9A, +0x28,0x8A,0x48,0x92,0xF3,0xA2,0x88,0x98,0x87,0xA1,0x88,0x80,0x81,0x95,0xD1,0xA3, +0x1B,0x1C,0x39,0x10,0xA1,0x2A,0x0B,0x7A,0x4B,0x80,0x13,0xC1,0xD1,0x2B,0x2A,0x85, +0xB2,0xA2,0x93,0xB2,0xD3,0x80,0xD1,0x18,0x08,0x08,0xB7,0x98,0x81,0x3F,0x01,0x88, +0x01,0xE2,0x00,0x9A,0x59,0x08,0x10,0xC3,0x99,0x84,0xA9,0xA5,0x91,0x91,0x91,0x80, +0xB5,0x94,0xC0,0x01,0x98,0x09,0x84,0xB0,0x80,0x7A,0x08,0x18,0x90,0xA8,0x6A,0x1C, +0x39,0x2A,0xB7,0x98,0x19,0x10,0x2A,0xA1,0x10,0xBD,0x39,0x18,0x2D,0x39,0x3F,0x10, +0x3F,0x01,0x09,0x19,0x0A,0x38,0x8C,0x40,0xB3,0xB4,0x93,0xAD,0x20,0x2B,0xD4,0x81, +0xC3,0xB0,0x39,0xA0,0x23,0xD8,0x04,0xB1,0x9B,0xA7,0x1A,0x92,0x08,0xA5,0x88,0x81, +0xE2,0x01,0xB8,0x01,0x81,0xC1,0xC7,0x90,0x92,0x80,0xA1,0x97,0xA0,0xA2,0x82,0xB8, +0x18,0x00,0x9C,0x78,0x98,0x83,0x0B,0x0B,0x32,0x7D,0x19,0x10,0xA1,0x19,0x09,0x0A, +0x78,0xA8,0x10,0x1B,0x29,0x29,0x1A,0x14,0x2F,0x88,0x4A,0x1B,0x10,0x10,0xAB,0x79, +0x0D,0x49,0x18,0xA0,0x02,0x1F,0x19,0x3A,0x2B,0x11,0x8A,0x88,0x79,0x8A,0x20,0x49, +0x9B,0x58,0x0B,0x28,0x18,0xA9,0x3A,0x7D,0x00,0x29,0x88,0x82,0x3D,0x1A,0x38,0xBA, +0x15,0x09,0xAA,0x51,0x8B,0x83,0x3C,0x8A,0x58,0x1B,0xB5,0x01,0xBB,0x50,0x19,0x99, +0x24,0xCA,0x21,0x1B,0xA2,0x87,0xA8,0xB1,0x68,0xA1,0xA6,0xA2,0xA8,0x29,0x8B,0x24, +0xB4,0xE2,0x92,0x8A,0x00,0x19,0x93,0xB5,0xB4,0xB1,0x81,0xB1,0x03,0x9A,0x82,0xA7, +0x90,0xD6,0xA0,0x80,0x1B,0x29,0x01,0xA4,0xE1,0x18,0x0A,0x2A,0x29,0x92,0xC7,0xA8, +0x81,0x19,0x89,0x30,0x10,0xE0,0x30,0xB8,0x10,0x0C,0x1A,0x79,0x1B,0xA7,0x80,0xA0, +0x00,0x0B,0x28,0x18,0xB1,0x85,0x1E,0x00,0x20,0xA9,0x18,0x18,0x1C,0x13,0xBC,0x15, +0x99,0x2E,0x12,0x00,0xE1,0x00,0x0B,0x3B,0x21,0x90,0x06,0xC9,0x2A,0x49,0x0A,0x18, +0x20,0xD1,0x3C,0x08,0x00,0x83,0xC9,0x41,0x8E,0x18,0x08,0x02,0xA0,0x09,0xA4,0x7B, +0x90,0x19,0x2A,0x10,0x2A,0xA8,0x71,0xBA,0x10,0x4A,0x0E,0x22,0xB2,0xB2,0x1B,0x8C, +0x78,0x1A,0xB5,0x93,0xA9,0x1B,0x49,0x19,0x29,0xA3,0xC6,0x88,0xAA,0x32,0x0D,0x1B, +0x22,0x08,0xC2,0x18,0xB9,0x79,0x3F,0x01,0x10,0xA9,0x84,0x1C,0x09,0x21,0xB0,0xA7, +0x0A,0x99,0x50,0x0C,0x81,0x28,0x8B,0x48,0x2E,0x00,0x08,0x99,0x38,0x5B,0x88,0x14, +0xA9,0x08,0x11,0xAA,0x72,0xC1,0xB3,0x09,0x8A,0x05,0x91,0xF2,0x81,0xA1,0x09,0x02, +0xF2,0x92,0x99,0x1A,0x49,0x80,0xC5,0x90,0x90,0x18,0x09,0x12,0xA1,0xF2,0x81,0x98, +0xC6,0x91,0xA0,0x11,0xA0,0x94,0xB4,0xF2,0x81,0x8B,0x03,0x80,0xD2,0x93,0xA8,0x88, +0x69,0xA0,0x03,0xB8,0x88,0x32,0xBC,0x97,0x80,0xB1,0x3B,0x1A,0xA6,0x00,0xD1,0x01, +0x0B,0x3B,0x30,0x9B,0x31,0x3E,0x92,0x19,0x8A,0xD3,0x5C,0x1B,0x41,0xA0,0x93,0xA2, +0xAF,0x39,0x4C,0x01,0x92,0xA8,0x81,0x3C,0x0D,0x78,0x98,0x00,0x19,0x0A,0x20,0x2D, +0x29,0x3C,0x1B,0x48,0x88,0x99,0x7A,0x2D,0x29,0x2A,0x82,0x80,0xA8,0x49,0x3E,0x19, +0x11,0x98,0x82,0x9A,0x3B,0x28,0x2F,0x20,0x4C,0x90,0x29,0x19,0x9A,0x7A,0x29,0x28, +0x98,0x88,0x33,0xCD,0x11,0x3A,0xC1,0xA4,0xA0,0xC4,0x82,0xC8,0x50,0x98,0xB2,0x21, +0xC0,0xB6,0x98,0x82,0x80,0x9C,0x23,0x00,0xF8,0x30,0xA8,0x1A,0x68,0xA8,0x86,0x9A, +0x01,0x2A,0x0A,0x97,0x91,0xC1,0x18,0x89,0x02,0x83,0xE0,0x01,0x8B,0x29,0x30,0xE2, +0x91,0x0B,0x18,0x3B,0x1C,0x11,0x28,0xAC,0x78,0x80,0x93,0x91,0xA9,0x49,0x8B,0x87, +0x90,0x99,0x3D,0x5A,0x81,0x08,0xA1,0x11,0x2F,0x1A,0x21,0x9B,0x15,0xA2,0xB0,0x11, +0xC0,0x91,0x5B,0x98,0x24,0xA2,0xF2,0x92,0x8B,0x6A,0x18,0x81,0xB5,0xB1,0x88,0x4C, +0x00,0x00,0xA4,0xC1,0x2B,0x1A,0x59,0x0A,0x02,0x80,0x1E,0x02,0x08,0xB3,0x80,0x9A, +0x23,0xB8,0xF2,0x84,0xAB,0x01,0x48,0x90,0xA7,0x90,0x0A,0x29,0x09,0x95,0x99,0xA0, +0x59,0x2B,0x00,0x97,0xB0,0x29,0x89,0x2A,0x03,0xD0,0xB7,0x1B,0x81,0x00,0xA6,0xB1, +0x90,0x09,0x48,0xC0,0x11,0x00,0x8A,0x00,0x5B,0x83,0x9A,0x18,0x2F,0x3C,0x18,0x11, +0xA9,0x04,0x1A,0x4F,0x01,0x98,0x81,0x09,0x09,0x4A,0x18,0xB4,0xA2,0x0B,0x59,0x90, +0x3B,0x49,0xBC,0x40,0x6A,0x88,0x3A,0x08,0x3E,0x3A,0x80,0x93,0xB0,0xE1,0x5A,0x00, +0xA4,0xB3,0xE3,0x90,0x0D,0x38,0x09,0x82,0xC4,0xA1,0xB1,0x4C,0x18,0x10,0x91,0xB2, +0x13,0xEA,0x34,0x99,0x88,0xA6,0x89,0x92,0x91,0xC1,0x20,0xB2,0xC2,0x86,0xD2,0xB3, +0x80,0xB2,0x08,0x09,0x87,0x91,0xC0,0x11,0x89,0x90,0x28,0xB9,0x79,0x19,0xA4,0x82, +0xD0,0x03,0x0C,0xA3,0xA5,0xB2,0xB2,0x1B,0x29,0x13,0xF1,0xB4,0x81,0x9D,0x38,0x00, +0xC4,0xA1,0x89,0x59,0x1A,0x81,0xA4,0xA9,0x1C,0x6A,0x19,0x02,0xB1,0x1A,0x4A,0x0B, +0x78,0x89,0x81,0x1C,0x2A,0x29,0x4A,0xA3,0x3E,0x1C,0x49,0x1A,0x08,0x21,0xAE,0x28, +0x4B,0x19,0x20,0x8C,0x10,0x3A,0xAB,0x26,0x8B,0x18,0x59,0x99,0x13,0xA2,0xAB,0x79, +0x2F,0x18,0x10,0xB2,0x80,0x1B,0x4D,0x5A,0x80,0x82,0x98,0x81,0x80,0x09,0xA5,0x90, +0x91,0x03,0xC2,0xE2,0x81,0xA8,0x82,0x09,0xC6,0xA3,0xB1,0x08,0x5B,0x08,0x05,0xD1, +0xA2,0x89,0x2A,0x28,0x91,0xA6,0x88,0xB0,0x49,0x80,0x09,0x08,0x88,0x07,0xB8,0x05, +0x99,0x81,0x88,0x18,0xE2,0x00,0xC3,0x18,0x0D,0x10,0x30,0xD0,0x93,0x8A,0x09,0x10, +0x2F,0x11,0x90,0xA1,0x20,0x9B,0xB1,0x73,0xC8,0x94,0x98,0x3B,0x01,0x0C,0x30,0x19, +0xF8,0x12,0x90,0xBA,0x78,0x0A,0x11,0x98,0xA0,0x79,0x8A,0x30,0x2B,0xC2,0x11,0x0D, +0x09,0x7A,0x00,0x82,0xB9,0x01,0x7A,0x89,0x21,0x09,0xA1,0x0A,0x7C,0x10,0x88,0xB5, +0x88,0x0A,0x2B,0x69,0x1A,0x10,0xA0,0x5B,0x19,0x1A,0x10,0x19,0x1A,0x6C,0x20,0x90, +0xA5,0x98,0x1B,0x0A,0x69,0x82,0xD1,0x18,0x09,0x19,0x2A,0x93,0xD4,0x9A,0x01,0x49, +0xA2,0xA2,0x82,0xD8,0x22,0xAA,0x97,0xA9,0x2D,0x38,0x2A,0xB6,0x80,0x90,0x0A,0x3C, +0x82,0x94,0xB8,0x21,0x0E,0x2A,0x22,0xB8,0x00,0x4F,0x2B,0x3A,0x81,0xA1,0x29,0x2C, +0x6A,0x13,0xD1,0xA2,0x98,0x28,0x0C,0x01,0xD5,0x08,0xA9,0x31,0xB3,0xB0,0xA7,0xB0, +0x29,0x1B,0x87,0xA2,0xA1,0xB2,0x4A,0x89,0x11,0xC3,0xF3,0x98,0x08,0x03,0xA0,0xA3, +0xC5,0x90,0xB3,0xB5,0xB4,0xB8,0x02,0x91,0x91,0xD3,0xA4,0xC1,0x1B,0x82,0x28,0xA4, +0xD1,0x94,0x8A,0x28,0x08,0x03,0xE0,0x80,0xD4,0x90,0x91,0xA1,0x3B,0x3D,0x02,0xE4, +0xA1,0x92,0x89,0x1A,0x4B,0x95,0xB3,0x90,0x99,0x6A,0x0A,0x30,0xA1,0x93,0xA6,0xA9, +0x85,0x8B,0x82,0x10,0xB1,0xA3,0x94,0xF8,0x38,0x9A,0x30,0x1A,0x8B,0xA7,0x89,0x01, +0x5B,0x19,0x18,0x11,0xF0,0x18,0x1C,0x39,0x19,0x0C,0x12,0x1C,0x2A,0x7B,0x3A,0x88, +0x2B,0x18,0x2B,0x5C,0x20,0x92,0x8D,0x38,0x8A,0x3A,0x5B,0x2E,0x3A,0x2B,0x10,0x12, +0xBB,0x6A,0x4D,0x18,0x10,0xB1,0x81,0x2A,0x8B,0x79,0x80,0x01,0x0A,0x09,0x5B,0x2D, +0x84,0x8A,0x08,0x02,0xA2,0x91,0x82,0xE8,0x50,0x9B,0x85,0xA3,0xB0,0xA3,0x1B,0x02, +0x18,0xF3,0xA2,0x88,0xAB,0x53,0xD1,0xB4,0xA3,0x09,0x09,0x18,0xD4,0x08,0xB0,0x09, +0x58,0xD1,0x82,0x89,0x81,0x1A,0x18,0x05,0xB9,0xC3,0x30,0xC0,0x95,0x80,0xC3,0x89, +0x89,0x13,0x88,0xF2,0x93,0x0E,0x18,0x01,0x92,0xA5,0xB8,0x2A,0x39,0xAA,0x33,0x9A, +0xB1,0x11,0xF5,0xA1,0xA1,0x0A,0x50,0xB8,0x03,0xC4,0xA0,0x4E,0x29,0x10,0x88,0xC2, +0x1A,0x39,0x1D,0x28,0x98,0x94,0x0E,0x10,0x2A,0x3C,0x02,0x2D,0x1B,0x4B,0x3B,0x49, +0x19,0xA9,0x48,0x2F,0x29,0x10,0x89,0x02,0x0C,0x10,0x09,0xB9,0x70,0x1B,0x8A,0x50, +0xA8,0x2B,0x49,0x89,0x69,0x88,0x95,0x89,0x90,0x92,0x4C,0x19,0x82,0xC1,0x01,0x80, +0xA0,0x2B,0x7A,0x81,0x10,0xC2,0xB7,0x98,0x88,0x19,0x2C,0x03,0xB1,0xA4,0xA1,0x0C, +0x3B,0x78,0x88,0x85,0xB1,0xA0,0x1B,0x3A,0x4A,0x08,0x94,0x81,0xF1,0x80,0x00,0x0C, +0x59,0x09,0x18,0x90,0xA6,0x92,0x8C,0x1A,0x79,0x92,0xA8,0x00,0x81,0x2E,0x2A,0x13, +0xA2,0xB0,0xA5,0x88,0x88,0x89,0x11,0x19,0xA0,0xF3,0x82,0xB0,0x83,0x5F,0x2A,0x01, +0xA1,0x94,0xB0,0x09,0x78,0x98,0xA3,0xA6,0xA0,0x91,0x80,0x93,0x98,0xC1,0x12,0x18, +0xC9,0x17,0xA0,0xA0,0x1A,0x21,0x80,0x99,0xD4,0x30,0x9D,0x00,0x10,0x2F,0x08,0x1C, +0x21,0x08,0xB4,0xC3,0x2B,0xA9,0x52,0xD2,0xA3,0xD1,0x09,0x10,0x8B,0x24,0x92,0xD1, +0x80,0x19,0xA0,0x2C,0x12,0x49,0xAA,0xB6,0x95,0xB8,0x08,0x3A,0x2B,0x01,0xF3,0xB3, +0x0B,0x09,0x79,0x18,0xA2,0xA4,0xA0,0x18,0x0C,0x20,0x08,0xA9,0x16,0x0C,0x00,0x1B, +0x08,0x2B,0x7B,0x01,0x01,0xB9,0x59,0x19,0x8B,0x45,0xA8,0x80,0x0C,0x1A,0x41,0x1E, +0x00,0x28,0xA8,0x5A,0x00,0xC1,0x49,0x99,0x21,0x1D,0x08,0x85,0x99,0x95,0x89,0x90, +0x11,0x90,0xD1,0x28,0xB2,0xA7,0x99,0x81,0x02,0xAC,0x13,0x81,0xB2,0xA6,0xA9,0x28, +0x1C,0xB1,0x33,0xD1,0xC1,0x58,0xA8,0x14,0xB0,0xB7,0x91,0xA0,0x82,0x89,0xC2,0x28, +0xA1,0xB2,0x49,0xD2,0x94,0xC8,0x12,0x80,0x99,0x85,0x08,0xD3,0x09,0xA2,0xB3,0x1E, +0x08,0x21,0xB9,0x23,0xB4,0xAB,0x41,0xAC,0x87,0x09,0xA2,0xC5,0x0B,0x2A,0x5A,0x91, +0x20,0x9A,0x89,0x78,0x9B,0x31,0x89,0x80,0x29,0x0A,0xB7,0x3C,0x98,0x48,0x1D,0x00, +0x01,0xB0,0x20,0x2F,0x29,0x4A,0x89,0x94,0x1C,0x88,0x28,0x2B,0x10,0x88,0x9A,0x71, +0x9A,0x08,0x4A,0x2F,0x18,0x2B,0x18,0x02,0xA8,0x4B,0x7A,0x99,0x48,0x80,0xA8,0x20, +0x1D,0x40,0xA8,0x10,0x08,0xA8,0xC5,0x88,0xC2,0x18,0x88,0x2A,0x12,0xF3,0x82,0xD8, +0x20,0x0A,0x09,0xA6,0x98,0x04,0xB9,0x11,0x18,0xC3,0xE1,0x29,0xA1,0x11,0xC1,0x03, +0xE2,0x9A,0x33,0xA9,0xB5,0x98,0x92,0xA1,0x02,0xF8,0x21,0xA8,0x10,0x02,0xC1,0xB7, +0x1B,0x90,0x5B,0x3C,0x83,0x93,0xE0,0x19,0x1A,0x11,0x11,0xF1,0x92,0x89,0x19,0x2C, +0x2C,0x41,0x99,0x92,0x90,0x3F,0x18,0x4B,0x00,0x08,0xD2,0x01,0xB2,0xAA,0x78,0x09, +0x01,0x91,0xA2,0x98,0x2F,0x3A,0x2C,0x01,0x00,0x93,0xE0,0x28,0x2C,0x2B,0x01,0x12, +0xE1,0x80,0xB3,0x3D,0x3A,0x0A,0x50,0x98,0xC2,0xA0,0x11,0xAA,0x30,0x87,0x90,0xC2, +0x29,0x88,0x38,0xC8,0xB5,0x90,0xBA,0x70,0x1A,0x02,0x94,0xD0,0x80,0x1A,0x82,0xA6, +0xB0,0x91,0x18,0xB3,0x00,0x13,0xF1,0xA2,0xC1,0x82,0xB0,0x00,0x15,0x0B,0xD3,0x02, +0xA8,0x91,0x2B,0x1F,0x49,0x88,0xA6,0x80,0x88,0x08,0x1B,0xA5,0x80,0xB9,0x06,0x0B, +0x90,0x21,0x9D,0x48,0x18,0xA0,0x15,0xC9,0x82,0x2B,0x1A,0x42,0x9A,0xC4,0x39,0xBC, +0x69,0x00,0xA0,0x29,0x8C,0x39,0x59,0x08,0x09,0x49,0xA9,0x6B,0x81,0x00,0x98,0xB0, +0x68,0x3D,0x81,0x88,0x18,0x19,0x1D,0x12,0x80,0xB2,0x3A,0x3F,0x85,0x92,0xD0,0x00, +0x0A,0x19,0x12,0xF1,0x02,0x9B,0x19,0x40,0xB9,0x11,0x02,0xF2,0x1A,0x08,0x94,0x0A, +0xC2,0x83,0x0B,0xB4,0xA4,0xC0,0x32,0xD8,0x86,0x98,0x90,0x95,0x89,0xA3,0x83,0xC2, +0x92,0xE1,0x92,0x82,0xD9,0x03,0x08,0xA9,0x85,0x92,0xA2,0x80,0xE0,0x30,0x8B,0xB3, +0x87,0x89,0x90,0x83,0xA0,0x08,0x92,0x93,0x3E,0xAB,0x43,0x89,0xE3,0x80,0x83,0x2F, +0x00,0xA3,0x80,0xC9,0x22,0x3F,0x08,0x81,0x0B,0x33,0x9A,0xA3,0x7B,0x0C,0x29,0x4A, +0x1B,0x21,0xAA,0x70,0x1B,0x0D,0x48,0x1A,0x81,0x88,0xB1,0x39,0x3F,0x08,0x58,0xA0, +0x81,0x1A,0x1A,0x2B,0x6D,0x11,0x0A,0x91,0x01,0x1A,0x98,0x5A,0x0C,0x03,0xB1,0x84, +0xA3,0xAD,0x58,0x2A,0xA1,0x84,0xB1,0xA0,0x5C,0x2B,0x13,0xA8,0x95,0x83,0xE8,0x10, +0x81,0xB0,0x00,0xC2,0x96,0xA0,0x91,0x00,0x2C,0x90,0x30,0xF2,0x80,0xA8,0x39,0x21, +0xC1,0x03,0xAC,0x39,0x7C,0x29,0x91,0x1A,0x00,0x19,0x2C,0x3A,0x93,0xB0,0x29,0x8F, +0x28,0x02,0x93,0xF3,0xA9,0x01,0x03,0xE0,0x08,0x09,0x1D,0x58,0xA1,0x83,0xA9,0x6B, +0x2A,0x3C,0x21,0x89,0xC2,0x2C,0x4B,0x8A,0x50,0x81,0x98,0xA8,0x32,0x0C,0x8E,0x24, +0x0B,0x1A,0x81,0x92,0xA1,0x4F,0x18,0x3A,0x0A,0xB4,0x18,0x2E,0x39,0x82,0x19,0xD3, +0xD0,0x28,0x1B,0x11,0x98,0x07,0xAA,0x28,0x00,0x88,0xB4,0x89,0x1B,0x1F,0x22,0x00, +0xB3,0xC9,0x33,0xAB,0x2B,0xB5,0x48,0x98,0x98,0xA7,0x10,0xD2,0xC1,0x23,0xCA,0x93, +0xC6,0x80,0xA1,0x88,0x02,0x89,0xE2,0x09,0x38,0xBA,0x40,0x89,0x21,0xD8,0x49,0x10, +0x8D,0x02,0x90,0xC3,0x9A,0x24,0x89,0x08,0x84,0xA5,0x9C,0x10,0x11,0x9C,0x88,0x30, +0x3C,0xA1,0x94,0x58,0x8C,0x0B,0x69,0x29,0x9A,0x81,0x12,0x2B,0x8B,0x79,0x94,0xB0, +0xC1,0x84,0xC2,0x99,0x25,0x99,0x11,0xA2,0x93,0xE4,0x99,0x80,0x0A,0x00,0x10,0xB7, +0xB0,0x31,0xBA,0x3C,0x21,0xB3,0xF1,0x18,0xA0,0x2A,0x20,0xA3,0x06,0xE8,0x28,0xA1, +0xB4,0x08,0x0B,0x11,0x4B,0xB7,0x90,0xA5,0x98,0x3D,0x19,0x02,0xA1,0xC4,0xB2,0x19, +0x28,0xC0,0xA5,0x92,0xB1,0xA3,0x0A,0x0A,0x08,0x2B,0x70,0xC4,0xB3,0x00,0xBC,0x4B, +0x39,0x12,0xE3,0xA0,0x00,0x3F,0x18,0x29,0x94,0xD1,0x19,0x09,0x00,0xA1,0x83,0x99, +0x9B,0x35,0x80,0xC4,0xB1,0x6A,0x1A,0x1C,0x29,0x38,0x0E,0x19,0x5A,0x1A,0x82,0x8A, +0x59,0x2A,0x2E,0x20,0x88,0xA8,0x3A,0x38,0x3D,0x00,0xB3,0x29,0xAD,0x49,0x10,0x0C, +0x01,0x01,0xA3,0x8F,0x85,0x09,0x1B,0x88,0x10,0xA3,0xD2,0x90,0x3C,0x5C,0x39,0x03, +0xD1,0xA0,0x00,0x2A,0x0B,0x04,0xA7,0x90,0xA0,0x11,0x90,0x99,0x83,0xB4,0xB1,0xF1, +0x84,0x88,0x90,0x18,0x18,0xD3,0xD2,0xB3,0xA0,0x1A,0x21,0xA7,0xB2,0xB3,0x92,0x9A, +0x22,0xB9,0x28,0x38,0xBD,0x87,0x2A,0xB1,0x13,0x0D,0x0A,0x38,0xC9,0x24,0xC0,0x19, +0x23,0x0F,0x01,0x88,0xC0,0x2A,0x82,0x18,0x28,0xF0,0x18,0x2A,0x29,0x4B,0x35,0xB8, +0xA3,0x9D,0x18,0x1B,0x40,0x00,0x9A,0x5C,0x3A,0x09,0x2F,0x38,0x8A,0x3B,0x3B,0x11, +0x5C,0x19,0x2B,0x4A,0x08,0x0A,0x3D,0x20,0x4F,0x3A,0x19,0x2A,0x18,0x4D,0x1B,0x3A, +0x11,0x0D,0x3A,0x3C,0x4B,0x93,0x81,0xAA,0x6B,0x4A,0x18,0x00,0xC3,0xC3,0x9A,0x59, +0x2A,0x1B,0xA7,0xA1,0x81,0x88,0x88,0x58,0xB2,0xB1,0x2B,0x83,0xD4,0x81,0x08,0x0F, +0x00,0x20,0xC2,0xE2,0x80,0x08,0x1C,0x29,0x04,0xB1,0xA2,0x01,0x1C,0x91,0x00,0x0C, +0x49,0xB0,0x43,0xF2,0x99,0x39,0x3F,0x00,0x81,0x94,0xC1,0x09,0x1A,0x69,0x90,0x80, +0x94,0xAA,0x20,0x2A,0x91,0xB1,0x39,0x7A,0x38,0xD1,0x10,0x8A,0x8C,0x5A,0x01,0xB5, +0x98,0x80,0x2A,0x0B,0x32,0x92,0xF1,0x81,0x9A,0x23,0x8A,0xA3,0xB7,0x09,0x03,0x08, +0xD0,0x94,0x9A,0x09,0x01,0x93,0xB7,0xC2,0x8C,0x3A,0x83,0x99,0x05,0xA0,0x0B,0x29, +0x93,0xE5,0x80,0x89,0x38,0x90,0x8A,0xD7,0xA1,0x19,0x1B,0x48,0x98,0x92,0xC3,0xA1, +0x09,0x3F,0x02,0x0C,0x22,0xC3,0xB2,0xA1,0x01,0x9F,0x4A,0x01,0xA3,0xD3,0xB0,0x28, +0x3F,0x29,0x20,0xA2,0xC2,0xB1,0x08,0x5A,0x98,0x13,0xD2,0xC1,0x01,0xB2,0x80,0x3D, +0x03,0xC1,0x89,0x96,0x90,0x90,0x3A,0x1A,0x9A,0x32,0xB6,0xA2,0x8E,0x4A,0x28,0x8A, +0x84,0xA2,0x8A,0x2D,0x49,0x09,0x88,0x18,0x30,0x9D,0x2C,0x23,0xB1,0x0C,0x92,0x2D, +0x39,0x82,0xC4,0x2E,0x10,0x1A,0x10,0xB9,0x48,0x19,0x39,0xBA,0x34,0xDA,0x2D,0x48, +0x1A,0xA6,0x98,0x83,0x9A,0x1D,0x38,0x04,0xD0,0x18,0x90,0x2C,0x11,0x93,0xD3,0x9A, +0x11,0x08,0x82,0xF1,0x01,0xA0,0x2A,0x93,0xD3,0xB4,0xB8,0x82,0x2F,0x11,0xA3,0xB3, +0xA8,0x3B,0x09,0x23,0x96,0xC8,0x3B,0x3F,0x93,0x82,0xA1,0x90,0x3F,0x28,0x81,0xD1, +0x93,0x08,0x2D,0x18,0x91,0xB3,0xB5,0x98,0x2A,0x2B,0x84,0xB1,0x5B,0x8A,0x31,0x18, +0x80,0x8B,0x7E,0x39,0x2B,0x02,0xC1,0x8B,0x6C,0x49,0x09,0x10,0xA1,0x08,0x01,0x0C, +0x20,0xA1,0x09,0x4F,0x18,0x00,0x01,0xA0,0x5C,0x1B,0x5B,0x10,0x92,0x90,0x2B,0x5A, +0x3D,0x18,0x91,0x19,0x98,0x2D,0x39,0x89,0x2D,0x3A,0x48,0x2C,0x11,0xB5,0x9A,0x19, +0x5B,0x28,0x90,0x95,0x98,0x89,0x2B,0x40,0x08,0x90,0xF3,0x0A,0x08,0xA6,0x80,0x91, +0xB2,0xA0,0x02,0xF2,0xA1,0xB7,0x89,0x81,0x82,0x91,0xB1,0x21,0xAB,0x32,0xE9,0x04, +0xA2,0x8D,0x12,0x91,0xA3,0xA3,0xD2,0x8B,0x39,0xD1,0x84,0xE2,0x90,0x00,0x2B,0x29, +0xA3,0xD4,0xA1,0x91,0x1D,0x5A,0x08,0x19,0x11,0x99,0x08,0x18,0x49,0x0F,0x18,0x10, +0x82,0xF1,0x00,0x89,0x2F,0x3A,0x01,0xB3,0xC2,0x81,0x3F,0x29,0x08,0x10,0xA1,0xA1, +0x3B,0x5D,0x19,0x28,0x0B,0x38,0x82,0x91,0x19,0xBD,0x3B,0x7A,0x80,0x12,0xB3,0xE0, +0x0B,0x6A,0x01,0x88,0xA4,0x08,0x0B,0x08,0x59,0x80,0x80,0x1D,0x49,0x89,0x00,0x84, +0x99,0x1A,0x2B,0x32,0xE3,0xB4,0xA9,0x3A,0x99,0x31,0xE3,0xAA,0x58,0x3B,0x88,0x95, +0xC0,0x18,0x4A,0x09,0x30,0xF2,0xA3,0x1C,0x1B,0x49,0x00,0xD3,0xB2,0xA0,0x18,0x11, +0x92,0xD3,0xB2,0x91,0x80,0xE7,0xA1,0x91,0x98,0x19,0x22,0xC2,0xD2,0x18,0x8D,0x3B, +0x10,0xA5,0x91,0x98,0x02,0x3E,0x80,0x01,0x90,0xAA,0x13,0xF1,0x02,0xD1,0x08,0x19, +0x49,0xB4,0x91,0xB4,0x99,0x2A,0x0C,0x32,0xC0,0x05,0x88,0x0B,0x80,0x2C,0x81,0x10, +0x0B,0x51,0xA9,0x19,0x05,0xBF,0x28,0x20,0xE1,0x90,0x80,0x28,0x19,0x08,0x26,0xB1, +0xA1,0x18,0x88,0x2A,0xF0,0x12,0x8A,0xB3,0x14,0x1B,0xD4,0xD8,0x10,0x08,0x8A,0x17, +0xA0,0x98,0x2B,0x3A,0x29,0x48,0xA4,0x99,0x0E,0x4A,0x12,0x8B,0x31,0x8B,0x4E,0x1A, +0x11,0xB5,0x89,0x91,0x29,0x89,0xC2,0x97,0x90,0x0A,0x19,0x11,0x91,0xC1,0xD5,0x08, +0x89,0x20,0x91,0xB1,0x1A,0x2D,0x18,0x29,0xD2,0x3B,0x3E,0x3A,0x2A,0x90,0x82,0x1C, +0x49,0x3B,0x93,0xB6,0xC8,0x4C,0x02,0x91,0x93,0xF2,0x88,0x2D,0x28,0x81,0x82,0xC1, +0x89,0x2D,0x6B,0x19,0x82,0x80,0x18,0x8B,0x39,0x39,0xC8,0x3A,0x6A,0x0A,0x22,0xD2, +0x09,0x2C,0x1A,0x68,0x92,0xE2,0x89,0x2A,0x2A,0x30,0xC2,0xA3,0xB4,0x1D,0x2A,0x09, +0x93,0x18,0xF2,0x89,0x28,0xB3,0x01,0x8F,0x18,0x11,0xA1,0x93,0x90,0xD1,0x7A,0x20, +0xC3,0xA2,0xA8,0x88,0x1D,0x28,0xA5,0xA2,0xA2,0x0B,0x29,0x2B,0x87,0xC1,0x80,0x0A, +0x19,0x01,0x12,0xF1,0x10,0x80,0x0A,0x18,0x08,0x2F,0x4A,0x02,0x89,0x1B,0x29,0x5D, +0x4C,0x08,0x82,0xA1,0x0A,0x3A,0x4B,0x29,0xC6,0xC3,0x09,0x09,0x88,0x39,0x98,0x82, +0xA5,0x1A,0x30,0x11,0xBD,0x3F,0x12,0x8B,0x28,0xC3,0x88,0x3F,0x2B,0x3B,0x48,0xA1, +0x80,0x8A,0x4D,0x39,0x01,0x93,0xA2,0xF1,0x19,0x19,0x0A,0x02,0xB2,0x8B,0x24,0xD2, +0x4B,0x12,0xC8,0x2E,0x10,0xB5,0x89,0x01,0x09,0x1C,0x2A,0x03,0xD4,0x91,0x98,0x99, +0x11,0x2B,0xE4,0x00,0x00,0x01,0xE0,0xA5,0x89,0x99,0x31,0x18,0xD0,0xB7,0x98,0x18, +0x0A,0x10,0x94,0xC2,0x90,0x18,0x00,0x99,0x87,0xA0,0x90,0x2A,0x3C,0x02,0xB8,0xC1, +0x79,0x1A,0x20,0x08,0xA1,0xD2,0x1C,0x29,0x03,0xD1,0x29,0x99,0x2C,0x50,0xB3,0xD1, +0x08,0x09,0x3C,0x10,0x04,0xB2,0x0D,0x2B,0x59,0x80,0x90,0x01,0x0F,0x3A,0x18,0x01, +0xA2,0x9B,0x5B,0x3D,0x81,0x03,0xD2,0x98,0x59,0x90,0x81,0x92,0xB4,0x8B,0x1B,0x40, +0xB2,0xB5,0x08,0x4B,0x01,0x09,0xD1,0x91,0x8B,0x7A,0x10,0xB3,0xC3,0x99,0x49,0x1A, +0x29,0xB5,0xA2,0xAB,0x40,0x81,0x19,0xB7,0xB0,0x20,0x2B,0xD4,0x88,0xA1,0x91,0x3C, +0x82,0x37,0xD3,0xB1,0x8A,0x1B,0x30,0xB3,0xF4,0xA1,0x91,0x09,0x10,0x03,0xD0,0x83, +0xA9,0x8F,0x10,0x01,0x90,0x18,0x80,0x20,0x2B,0xF1,0x28,0x99,0x2A,0x41,0xF0,0x12, +0xAA,0x83,0x82,0xD1,0xC1,0x08,0x89,0x59,0x09,0x83,0x87,0xB0,0x2A,0x4D,0x18,0x09, +0x19,0xB3,0x4B,0x3F,0x39,0x19,0x09,0x01,0x89,0x03,0x1F,0x00,0x1A,0x0B,0x10,0x68, +0xA0,0x18,0x8C,0x6A,0x09,0x08,0x97,0xA1,0x81,0x1B,0x2B,0x4C,0x03,0xB4,0xA8,0x92, +0x4B,0x3C,0xA1,0x81,0x95,0xA8,0x81,0x12,0xBB,0x92,0x45,0xB9,0x93,0xF4,0x88,0x0A, +0x2D,0x28,0x00,0xA3,0xA3,0x8A,0x3F,0x48,0xB1,0x92,0xB4,0xA8,0x30,0x80,0xD3,0x80, +0xD1,0x19,0x3B,0xC4,0x81,0xC1,0x29,0x0D,0x20,0x13,0xC8,0xB4,0x4C,0x09,0x00,0x82, +0xC2,0x3B,0x0D,0x30,0x0B,0x12,0xF0,0x1B,0x20,0x0A,0xA6,0x80,0x0A,0x4A,0x4A,0x80, +0x94,0xB1,0x2E,0x3B,0x1A,0x10,0x93,0x10,0x4C,0x3D,0x08,0x82,0xC9,0x19,0x6A,0x2B, +0x38,0xD1,0x08,0x19,0x2A,0x5A,0x82,0xB1,0x8D,0x29,0x78,0x09,0x82,0x0A,0x2C,0x1B, +0x19,0x41,0xB8,0x8C,0x79,0x2B,0x11,0x88,0x82,0x91,0xDC,0x28,0x11,0xB0,0x11,0x18, +0xC9,0x62,0xA1,0x91,0x98,0x3B,0x3A,0xB0,0xF4,0x01,0xC0,0x29,0x39,0xF8,0x95,0x91, +0x88,0x88,0x91,0x03,0xA1,0xE2,0x18,0x82,0xD1,0xA2,0xD1,0x80,0x19,0x20,0x83,0xB1, +0xE3,0x80,0x91,0x4D,0x1A,0x03,0xB2,0x09,0x18,0xD1,0x19,0x09,0x92,0xA6,0xA0,0xB6, +0xB2,0x8B,0x38,0x10,0x42,0xD3,0xD0,0xA8,0x20,0x2C,0x10,0x01,0xB1,0xB4,0xAB,0x5B, +0x79,0x80,0x10,0x1A,0xA8,0x3D,0x18,0x20,0xB3,0x8F,0x18,0x01,0x00,0x09,0xF3,0x89, +0x69,0x88,0x81,0x91,0x08,0xE1,0x1A,0x08,0x11,0x81,0x1E,0x29,0xA0,0x01,0x00,0x90, +0x3E,0x7B,0x18,0x82,0xC3,0xA1,0x2A,0x2C,0x5B,0x81,0xA5,0x90,0x81,0x00,0x0B,0x1A, +0x1C,0x2C,0x32,0xC0,0xF3,0x80,0x2D,0x2A,0x10,0x02,0xE4,0xC1,0x89,0x4A,0x09,0x01, +0x03,0xD2,0x98,0x2A,0x39,0x8A,0x89,0x26,0xB1,0xB2,0x12,0xC0,0x0A,0x5A,0x18,0x98, +0xF3,0x92,0x99,0x99,0x79,0x01,0xB5,0xA1,0x80,0x80,0x90,0x83,0xA0,0xE2,0x81,0x29, +0x93,0x8A,0x0A,0x6A,0x1F,0x18,0x02,0xC8,0x01,0x19,0x3B,0x4A,0x98,0x17,0xA8,0x0D, +0x38,0xA1,0x91,0x10,0xA2,0x2B,0x4C,0xA6,0x81,0xBA,0x21,0x4C,0x80,0x21,0xD1,0x92, +0x2C,0x08,0x30,0x9F,0x93,0x2A,0x89,0x03,0x8B,0x87,0x0A,0x0D,0x12,0x98,0xA4,0x93, +0xBB,0x59,0x18,0xA1,0x32,0xE9,0x84,0x08,0x8A,0x02,0xA1,0x91,0x4B,0xB4,0x20,0x88, +0xF0,0x3A,0x1A,0x88,0x87,0xB1,0x92,0x0A,0x08,0x6B,0x83,0xC3,0x91,0xC0,0x2B,0x79, +0x08,0x8A,0x84,0xA0,0x89,0x40,0x1B,0xA1,0x39,0x98,0x17,0xC2,0xA2,0x12,0xCD,0x20, +0x89,0x92,0x25,0xB0,0x2D,0x3A,0x8B,0x58,0x2A,0xA0,0x4C,0x08,0x30,0xAE,0x82,0x59, +0x89,0x1A,0x10,0xC2,0x18,0x2C,0x40,0x1E,0x01,0xA3,0x8A,0x81,0x2C,0x29,0x29,0xA9, +0x13,0x51,0xAD,0x12,0x89,0x8F,0x18,0x2C,0x39,0x00,0xC1,0x10,0x3C,0x2A,0x41,0xC8, +0xA2,0x91,0x0A,0x6C,0x10,0x12,0x88,0xE8,0x30,0x91,0x81,0xD8,0x01,0x1B,0x0D,0x07, +0x00,0xA8,0x92,0x0A,0x28,0xD2,0xC3,0x02,0xAA,0x94,0x81,0xB4,0xB3,0x1A,0x0B,0x13, +0xF9,0x16,0xA1,0x8A,0x59,0x19,0x02,0xC1,0x91,0x8B,0x3D,0x18,0x3B,0xA4,0x94,0x80, +0x99,0x88,0x1C,0x79,0x0A,0x02,0x03,0xF8,0x90,0x39,0x5B,0x19,0x02,0xC3,0x90,0xBB, +0x58,0x6A,0x09,0x02,0x89,0x91,0x88,0x1A,0x69,0x8A,0x19,0x15,0xA0,0xA2,0x00,0x9A, +0x6B,0x49,0x88,0xA3,0x92,0xBB,0x6B,0x3D,0x38,0x01,0x98,0x91,0x3F,0x09,0x18,0x20, +0x90,0x80,0xAC,0x70,0x91,0x9B,0x51,0x09,0x88,0x99,0x14,0x8B,0x98,0x83,0x79,0xA0, +0x99,0x13,0x01,0x19,0xE0,0x83,0x0B,0xB0,0x0C,0x31,0x95,0xB5,0xC2,0x8A,0x39,0x20, +0x80,0x39,0xF3,0xB1,0x10,0x88,0x5E,0x18,0x94,0xA1,0x88,0xA1,0x98,0x15,0xAA,0x39, +0xD4,0x84,0xC0,0xA2,0xA2,0x0C,0x81,0x86,0xB5,0xA1,0xB1,0x14,0x1B,0xB1,0x02,0x92, +0xC3,0xE0,0x88,0x11,0xAA,0x69,0x18,0x81,0xA3,0xB0,0x01,0xBF,0x2A,0x31,0x93,0xF1, +0x00,0x89,0x18,0x19,0x11,0xD3,0xE0,0x10,0x18,0xB1,0x18,0x24,0x9A,0x2B,0xA4,0xC0, +0xB0,0x31,0x6C,0x19,0xB4,0x12,0xA8,0xEA,0x58,0x10,0x8B,0x93,0x82,0x88,0x9A,0x41, +0x10,0xC3,0xEA,0x41,0xA9,0x9C,0x34,0xA1,0x2A,0x79,0xA2,0x01,0xA8,0xB3,0x28,0xCC, +0x41,0x9A,0xB3,0x4B,0xB3,0x27,0x8B,0x83,0x2B,0x2F,0x08,0x28,0xB2,0x80,0x2C,0x30, +0x5E,0x09,0x12,0x9B,0x09,0x22,0x5B,0x19,0x8A,0x11,0x59,0x99,0xA4,0x32,0xCD,0x18, +0x08,0x10,0x85,0xB3,0xB4,0x1E,0x88,0x28,0x8A,0x11,0x09,0xC0,0x79,0x80,0x91,0x3B, +0x80,0x10,0x0F,0x01,0x80,0x91,0x19,0x3D,0x92,0x28,0xA8,0x37,0x9A,0x0A,0x3A,0x8A, +0x45,0xA9,0xA4,0x00,0xAA,0x09,0x3D,0x59,0x20,0xE1,0x08,0x98,0x90,0x59,0x10,0x09, +0xA3,0xC3,0x93,0x99,0x2B,0x69,0x11,0xD1,0xB1,0xA4,0x91,0x3C,0x89,0x83,0xF0,0x10, +0x91,0xA1,0x89,0x59,0x05,0x99,0x93,0x94,0xC8,0x08,0x0A,0x09,0x17,0xB1,0x83,0xC1, +0x91,0x40,0xA2,0xC2,0x98,0xC3,0xBA,0x28,0x23,0x0F,0x80,0x50,0xB8,0x19,0x10,0x96, +0x98,0x8C,0x05,0x98,0x19,0x29,0x2B,0x3B,0x0A,0xE2,0x01,0x0F,0x3C,0x38,0x08,0x09, +0x81,0x4A,0x6C,0x08,0x00,0x88,0x98,0x38,0x2C,0x5A,0x1B,0x20,0x1A,0x39,0xB0,0x09, +0xCB,0x5B,0x49,0x09,0x71,0x00,0xC1,0x0E,0x08,0x38,0x0C,0x02,0x10,0x0E,0x10,0x8A, +0x48,0x19,0x90,0x92,0x0D,0xA3,0x98,0x3B,0x79,0x19,0x01,0x10,0xE1,0x80,0x19,0x2B, +0x10,0xF2,0x02,0xAB,0x84,0x9A,0x29,0xB4,0x80,0x92,0x03,0x88,0x95,0xD0,0x03,0x90, +0xA0,0xC7,0xA1,0xB0,0xA2,0x02,0x18,0xB5,0xD4,0x01,0xC0,0x08,0xA2,0x93,0xA8,0xA0, +0xC3,0x20,0xF3,0x90,0x00,0xD5,0x08,0x89,0xA5,0x80,0xA0,0x81,0x82,0xC2,0x09,0xD1, +0x13,0xCB,0x03,0x84,0x91,0xE1,0x1B,0x12,0x08,0xAB,0x87,0x18,0xAB,0x58,0x89,0x28, +0x81,0xC9,0x33,0xA9,0x80,0x2E,0x20,0x83,0xB9,0x20,0x3B,0x9E,0x7A,0x08,0x81,0x18, +0x0B,0x88,0x79,0x80,0x8B,0x00,0x12,0x0E,0x89,0x51,0x1B,0x81,0xA0,0x3A,0x01,0xAF, +0x11,0x28,0xBA,0x35,0x98,0x88,0x52,0xC0,0x83,0x2F,0xA9,0x11,0x0A,0x19,0x25,0xD0, +0x30,0x9C,0x08,0x21,0x98,0x81,0x2A,0xF3,0x2A,0x80,0xB6,0x2B,0x08,0x93,0xE9,0x02, +0x81,0x8C,0x21,0x00,0xA6,0xA9,0x94,0x01,0x8F,0x80,0x94,0x98,0x93,0xB4,0x00,0x08, +0xC0,0x14,0x98,0xB3,0xB4,0xC1,0x09,0x18,0xA7,0x00,0xA3,0xC8,0x0A,0x3C,0x19,0x96, +0x83,0xC1,0x99,0x19,0x4A,0x85,0x80,0xC1,0x91,0x99,0x90,0x2A,0x17,0x95,0x99,0x88, +0x12,0xAE,0x39,0x08,0x92,0x84,0xB0,0xA8,0x79,0x09,0x19,0x01,0xB2,0xA3,0x8F,0x28, +0x2B,0xA2,0x40,0x82,0xA0,0x4C,0xA9,0x39,0x8D,0x81,0x70,0x88,0xA0,0x1A,0x49,0x2D, +0x1A,0x26,0xA8,0x98,0x08,0x29,0x0B,0x12,0x96,0xB1,0xB2,0x3A,0x13,0x9B,0x60,0xA0, +0x88,0xB2,0x34,0xEA,0x1A,0x2A,0x79,0x98,0x10,0x04,0x8C,0x1C,0x81,0x04,0x8C,0x83, +0x19,0x2F,0x81,0x93,0x98,0x10,0x08,0x30,0x2A,0xFA,0x05,0x08,0x2A,0x89,0x91,0xA3, +0xFA,0x11,0x11,0x00,0x8C,0x04,0x8A,0x2A,0xB5,0x10,0xA9,0xC2,0x3D,0x1B,0x32,0x04, +0x0A,0x1A,0x09,0x40,0x1F,0x92,0x1D,0x2A,0x91,0x10,0x30,0x2F,0x0B,0x68,0x99,0xA2, +0x92,0x88,0x78,0xA9,0x20,0x28,0xE2,0x92,0x1A,0x99,0x4B,0x19,0x22,0xA1,0xE2,0x21, +0x2F,0x98,0x29,0x18,0x91,0x08,0xB0,0x79,0x1A,0x82,0x3B,0xB1,0xA7,0x8A,0xB3,0x98, +0x5B,0x23,0xCA,0x42,0x83,0xF0,0x90,0x18,0x98,0x08,0xB4,0x20,0xA3,0xC0,0x43,0xD8, +0x80,0x81,0xA3,0x99,0xD9,0xA7,0x19,0x90,0x10,0x05,0xB1,0x8B,0x02,0xA4,0xBD,0x23, +0x93,0x8A,0x99,0x4B,0x03,0xC1,0xF8,0x38,0x09,0x2B,0x14,0xD0,0x03,0x8A,0x2A,0x39, +0xB9,0x97,0x90,0xAA,0x50,0x01,0x99,0x51,0xD1,0x09,0x1A,0xB5,0x00,0x8B,0x93,0x08, +0x98,0x11,0xF9,0x85,0x2B,0x08,0x96,0x89,0x90,0x2A,0x12,0x4A,0xD8,0x85,0x2B,0x0E, +0x10,0x00,0x01,0xB1,0x9B,0x69,0x1A,0x90,0x40,0xB8,0x01,0x08,0x0A,0x2C,0x09,0x14, +0x4B,0xE2,0x82,0x88,0xB1,0x78,0x0A,0x01,0xC2,0x93,0x19,0xCE,0x20,0x3C,0x82,0xB4, +0x1B,0x20,0x8C,0x3B,0x29,0xAB,0x86,0x23,0xD8,0x81,0x9A,0x5A,0x49,0xB0,0x16,0xA0, +0xB0,0x28,0x1B,0x13,0x93,0xE4,0xA2,0xA9,0x08,0x5A,0xB3,0x12,0xC1,0xE1,0x10,0x88, +0x01,0x0C,0x92,0x08,0x89,0xB7,0x88,0x81,0x10,0x9A,0x17,0xA0,0xB0,0x13,0x99,0xE0, +0x39,0x31,0xD2,0xB2,0x80,0x0B,0x2D,0x49,0x80,0x01,0xB0,0x06,0x09,0x0C,0x3A,0x69, +0xA0,0x08,0xB2,0xA1,0x69,0x2B,0x5A,0x81,0x92,0xBA,0x21,0xB1,0x7D,0x10,0x80,0x08, +0x88,0x82,0x32,0x0D,0xB0,0x1A,0x1C,0x21,0x94,0xA9,0x58,0xB9,0x5A,0x4A,0xA0,0x13, +0xA9,0x80,0x7C,0x00,0x20,0x8A,0x04,0x0C,0x00,0x82,0x2A,0xB2,0xAC,0x4B,0x69,0xA0, +0xA6,0x81,0x9B,0x19,0x38,0x8B,0x17,0xB2,0x81,0x2A,0xBB,0x94,0x29,0xA2,0x15,0xBA, +0x97,0xA3,0xB9,0x79,0x01,0xB2,0x02,0xF1,0x90,0x0A,0x29,0x11,0x88,0xE5,0xA0,0x81, +0x19,0x91,0x90,0x28,0xB3,0x14,0xD0,0xB5,0x91,0x9A,0x29,0x0B,0x07,0xA2,0xB3,0x01, +0x9D,0x28,0x41,0xD0,0x91,0x90,0x82,0x1A,0xA8,0x44,0x9A,0xA9,0x21,0xE3,0xA9,0x4B, +0x19,0x78,0x89,0x83,0xA3,0xB9,0x5A,0x3D,0x80,0x82,0xA2,0xA0,0x6C,0x10,0x20,0x8B, +0x93,0x8B,0x0E,0x33,0xA9,0xB1,0x68,0x8A,0x31,0xAC,0x94,0xB4,0x8B,0x32,0x0B,0xB4, +0x81,0x91,0x1D,0x33,0xD9,0x31,0xE1,0x8B,0x3B,0x30,0x12,0x49,0xD2,0x8E,0x29,0x18, +0x8A,0x92,0x02,0xAA,0x59,0x1C,0x32,0x88,0x01,0x23,0xFB,0x83,0x29,0xDA,0x59,0x01, +0x81,0x92,0xE1,0x18,0x8A,0x1D,0x30,0x93,0xF1,0x00,0x01,0x0B,0x39,0x92,0x89,0xA0, +0x11,0x5B,0xE0,0x82,0x09,0x13,0xAA,0xB4,0x16,0xD8,0x91,0x2A,0x29,0x84,0x1B,0xC5, +0x98,0x98,0x31,0x98,0x99,0x17,0xA9,0x20,0x92,0xC3,0x18,0x9D,0x20,0x3D,0x89,0x94, +0xA2,0x1C,0x5C,0x29,0x39,0xA0,0xB3,0x00,0x0C,0x4C,0x48,0x92,0x0A,0x91,0x85,0x9A, +0x01,0x82,0x1F,0x10,0x99,0x15,0xC1,0xA0,0x39,0x1A,0x1D,0x85,0xB4,0x90,0x1A,0x2A, +0x4B,0x01,0xB2,0x93,0xBE,0x12,0x83,0xC9,0x18,0x09,0x20,0x78,0xF1,0x08,0x19,0x88, +0x3A,0x83,0xB3,0xA9,0x93,0x7A,0x0A,0x96,0x98,0x00,0xA8,0x3A,0x30,0x92,0xF2,0x9B, +0x3D,0x38,0x92,0x92,0xC3,0xB8,0x6B,0x29,0x01,0x01,0xB2,0x2F,0x09,0x19,0x18,0x01, +0x3B,0x7B,0x10,0xA1,0x90,0x39,0x0F,0x38,0x0A,0xB5,0xA4,0x89,0x8B,0x6A,0x2B,0x12, +0xC8,0x90,0x40,0x2A,0x9E,0x22,0x88,0x18,0x09,0x3A,0xC3,0xE8,0x09,0x59,0x08,0x12, +0x94,0xD0,0x1A,0x2C,0x38,0x00,0xA1,0x83,0xE8,0x08,0x3A,0x08,0x10,0x9E,0x83,0x1D, +0x92,0x19,0x2C,0x39,0x3B,0x59,0x04,0xE1,0x80,0x08,0x8D,0x21,0x81,0xB2,0xB2,0x02, +0x99,0x91,0xA4,0xD6,0x98,0x99,0x03,0x80,0x98,0xA7,0x91,0x09,0xA1,0xB2,0xB3,0xE1, +0x12,0x92,0xB1,0x81,0x06,0x99,0x0A,0x23,0xC4,0xB1,0xF2,0x89,0x19,0x3A,0x94,0x82, +0xE0,0x89,0x38,0x0B,0xA4,0xA5,0x80,0x80,0x8C,0x34,0xB9,0xA9,0x23,0x13,0xB9,0xC1, +0xC7,0x1B,0x89,0x10,0x20,0x11,0xE3,0xA8,0x4B,0x0B,0x40,0x91,0x90,0x1B,0x5F,0x2A, +0x18,0x82,0x91,0x0B,0x4A,0x28,0xCA,0x40,0x80,0x5B,0x2C,0x13,0xB0,0x8A,0xA9,0x5A, +0x58,0x89,0x82,0x88,0x2E,0x3B,0x31,0xA1,0x9B,0x01,0x7A,0x2C,0x01,0x91,0x93,0x3F, +0x88,0x39,0x10,0xF1,0x91,0x8B,0x48,0x0A,0x12,0xE3,0xA8,0x18,0x28,0x92,0x97,0x98, +0x99,0x19,0xA1,0x11,0xB6,0x88,0x3B,0x10,0xD3,0xC3,0xA1,0x2A,0x8A,0x49,0x04,0xF1, +0x91,0x02,0x8A,0x89,0x04,0xF1,0x98,0x80,0x18,0x12,0xE3,0x81,0x98,0x80,0x01,0xB3, +0xF2,0x99,0x12,0x2A,0xB5,0xB3,0x92,0xAA,0x19,0x50,0xB2,0xC3,0x92,0xD0,0x2B,0x68, +0x93,0x99,0xC0,0x2C,0x3E,0x80,0x20,0x08,0x93,0x0D,0x2A,0x31,0x8D,0x02,0x2B,0x91, +0x08,0x0A,0x03,0x2C,0x3C,0x52,0xB9,0xA0,0x12,0xBF,0x3A,0x29,0x01,0x88,0xC0,0x6A, +0x3C,0x0A,0x49,0x18,0x0B,0x39,0x2B,0x69,0x0A,0x84,0x2A,0x2A,0x1C,0x2A,0xC3,0x8C, +0x19,0x50,0x09,0x91,0xA7,0x8D,0x18,0x1A,0x28,0x00,0xA0,0x94,0x10,0x1F,0x20,0x90, +0x8A,0x12,0xD0,0x1A,0x5A,0x81,0x04,0xBC,0x23,0x10,0xE0,0x90,0x90,0x18,0x1A,0xA6, +0x12,0xB1,0xD0,0x4A,0x08,0x82,0x92,0xB6,0x9A,0x0A,0x12,0x88,0xC3,0xC5,0x8A,0x89, +0x20,0xB5,0x93,0x0B,0x18,0x00,0x09,0xF2,0x88,0x2A,0x4A,0x08,0x05,0xB2,0xA9,0x3B, +0x5D,0x28,0xA4,0xB1,0x00,0x19,0x19,0x7A,0xA3,0xB3,0x0A,0x90,0xA1,0xC4,0x80,0xBA, +0x50,0x13,0xC1,0xC2,0x9A,0x2A,0x7B,0x28,0x84,0xC1,0x09,0x3B,0x4E,0x20,0x91,0xA1, +0x18,0xAB,0x79,0x10,0xB4,0x08,0x9A,0x11,0x2B,0xF0,0x93,0xAA,0x01,0x6A,0x01,0x93, +0x80,0xB8,0x2A,0x5B,0x10,0x80,0x89,0x4A,0x5B,0x92,0x15,0xB2,0xA0,0x2F,0x19,0x93, +0xB8,0x95,0x80,0x1C,0x21,0xA9,0x02,0x0B,0xA0,0x5A,0x18,0x98,0x39,0x1B,0x68,0x00, +0x91,0x91,0x9C,0x39,0x3E,0x18,0x84,0xB3,0x9B,0x7A,0x08,0x18,0x0A,0xB5,0x91,0x0B, +0x28,0x39,0x19,0x90,0x0A,0x50,0xAC,0x11,0x01,0xAB,0x88,0x52,0x1B,0x83,0xC4,0xA2, +0x9A,0xAB,0x03,0x90,0x19,0x93,0x81,0x08,0x92,0x9A,0x68,0x98,0x19,0x39,0xC1,0x92, +0x8A,0x38,0x4E,0x02,0xB1,0x90,0xC3,0x18,0x2B,0x04,0xC3,0xD2,0x91,0x90,0x81,0x89, +0x13,0xF1,0x88,0x93,0xA2,0x00,0x91,0xC0,0x5B,0x21,0x99,0x93,0x06,0x9A,0x1B,0x48, +0x99,0xB7,0x90,0x89,0x18,0x1B,0x11,0xA4,0xB2,0x81,0x9A,0x08,0x97,0x98,0x91,0x10, +0xB8,0x06,0xA2,0xA0,0x29,0x2B,0x21,0xC2,0xD1,0x10,0x1A,0x4A,0x29,0xF1,0x98,0x29, +0x1B,0x31,0x10,0xA0,0xA1,0x1D,0x5A,0x29,0xB2,0x82,0xA8,0x0F,0x28,0x21,0x09,0x91, +0x82,0x4D,0x10,0xA3,0xB0,0x89,0x4C,0x39,0xA0,0xA4,0xA1,0x89,0x1E,0x28,0x29,0xA3, +0xC3,0x2D,0x19,0x01,0x49,0x01,0x9B,0x0C,0x21,0xC2,0xA2,0x93,0x7C,0x2A,0x10,0x90, + +/* Source: 08HH.ROM */ +/* Length: 384 / 0x00000180 */ + +0x75,0xF2,0xAB,0x7D,0x7E,0x5C,0x3B,0x4B,0x3C,0x4D,0x4A,0x02,0xB3,0xC5,0xE7,0xE3, +0x92,0xB3,0xC4,0xB3,0xC3,0x8A,0x3B,0x5D,0x5C,0x3A,0x84,0xC2,0x91,0xA4,0xE7,0xF7, +0xF7,0xF4,0xA1,0x1B,0x49,0xA5,0xB1,0x1E,0x7F,0x5A,0x00,0x89,0x39,0xB7,0xA8,0x3D, +0x4A,0x84,0xE7,0xF7,0xE2,0x2D,0x4C,0x3A,0x4E,0x7D,0x04,0xB0,0x2D,0x4B,0x10,0x80, +0xA3,0x99,0x10,0x0E,0x59,0x93,0xC4,0xB1,0x81,0xC4,0xA2,0xB2,0x88,0x08,0x3F,0x3B, +0x28,0xA6,0xC3,0xA2,0xA2,0xC5,0xC1,0x3F,0x7E,0x39,0x81,0x93,0xC2,0xA3,0xE5,0xD2, +0x80,0x93,0xB8,0x6D,0x49,0x82,0xD4,0xA1,0x90,0x01,0xA0,0x09,0x04,0xE3,0xB2,0x91, +0xB7,0xB3,0xA8,0x2A,0x03,0xF3,0xA1,0x92,0xC5,0xC3,0xB2,0x0B,0x30,0xB3,0x8E,0x6D, +0x4A,0x01,0xB4,0xB4,0xC4,0xC3,0x99,0x3B,0x12,0xE3,0xA1,0x88,0x82,0xB4,0x9A,0x5C, +0x3A,0x18,0x93,0xC3,0xB3,0xB4,0xA8,0x19,0x04,0xF3,0xA8,0x3B,0x10,0xA2,0x88,0xA5, +0xB2,0x0B,0x6D,0x4B,0x10,0x91,0x89,0x3C,0x18,0x18,0xA6,0xC4,0xC3,0x98,0x19,0x2B, +0x20,0x91,0xA0,0x4E,0x28,0x93,0xB3,0xC2,0x92,0xA9,0x5A,0x96,0xC4,0xC2,0x09,0x01, +0xC4,0xA1,0x92,0xC4,0xA1,0x89,0x10,0xA3,0xA1,0x90,0x1C,0x5A,0x01,0xC5,0xA1,0x92, +0xD4,0xB3,0xC4,0xC4,0xC3,0xA1,0x88,0x1A,0x28,0x89,0x3C,0x3A,0x3D,0x29,0x00,0x93, +0xB0,0x3D,0x28,0x80,0x91,0x82,0xE3,0x99,0x2A,0x11,0xD6,0xC3,0x99,0x29,0x82,0xC4, +0xC3,0xA1,0x0A,0x3B,0x3D,0x3A,0x02,0xC3,0xA2,0x99,0x3B,0x2C,0x7C,0x28,0x81,0xA3, +0xB2,0xA3,0xB1,0x08,0x1A,0x3C,0x18,0x2E,0x4C,0x39,0xA5,0xB3,0xB4,0xC2,0x88,0x08, +0x19,0x0A,0x49,0xB7,0xB3,0xA2,0xA1,0x92,0xA1,0x93,0xB1,0x0C,0x7D,0x39,0x93,0xB3, +0xB1,0x1A,0x19,0x5D,0x28,0xA6,0xC4,0xB2,0x90,0x09,0x2A,0x18,0x1B,0x5B,0x28,0x88, +0x2C,0x29,0x82,0xA0,0x18,0x91,0x2D,0x29,0x2B,0x5C,0x4C,0x3B,0x4C,0x28,0x80,0x92, +0x90,0x09,0x2B,0x28,0x1D,0x6B,0x11,0xC5,0xB2,0x0B,0x39,0x09,0x4D,0x28,0x88,0x00, +0x1B,0x28,0x94,0xE3,0xA0,0x1A,0x28,0xB5,0xB4,0xB3,0xB2,0x93,0xE2,0x91,0x92,0xD4, +0xA0,0x1B,0x4A,0x01,0xA1,0x88,0x2D,0x5C,0x3B,0x28,0x08,0x93,0xD4,0xB2,0x91,0xB4, +0xA0,0x3E,0x3B,0x4B,0x3B,0x29,0x08,0x93,0x9B,0x7B,0x3A,0x19,0x00,0x80,0x80,0xA0, + +/* Source: 10TOM.ROM */ +/* Length: 640 / 0x00000280 */ + +0x77,0x27,0x87,0x01,0x2D,0x4F,0xC3,0xC1,0x92,0x91,0x89,0x59,0x83,0x1A,0x32,0xC2, +0x95,0xB1,0x81,0x88,0x81,0x4A,0x3D,0x11,0x9E,0x0B,0x88,0x0C,0x18,0x3B,0x11,0x11, +0x91,0x00,0xA0,0xE2,0x0A,0x48,0x13,0x24,0x81,0x48,0x1B,0x39,0x1C,0x83,0x84,0xA1, +0xD1,0x8E,0x8A,0x0B,0xC0,0x98,0x92,0xB8,0x39,0x90,0x10,0x92,0xF0,0xB5,0x88,0x32, +0x49,0x51,0x21,0x03,0x82,0x10,0x8A,0x7A,0x09,0x00,0xA2,0xCA,0x1B,0xCC,0x1C,0xB9, +0x8E,0x89,0x89,0xA1,0x89,0x92,0x29,0x11,0x60,0x40,0x14,0x22,0x32,0x78,0x40,0x01, +0x02,0x90,0x81,0xAB,0x0B,0x00,0xAF,0x99,0xCC,0xAB,0xDA,0xA9,0x99,0x1B,0x30,0x14, +0x92,0x22,0x19,0x68,0x32,0x14,0x26,0x13,0x23,0x23,0x20,0x12,0x9A,0xA8,0xB9,0xFA, +0xAA,0xCA,0xCC,0x0C,0xA8,0xAE,0x88,0xB9,0x88,0xA0,0x02,0x21,0x50,0x43,0x03,0x81, +0x2A,0x11,0x34,0x63,0x24,0x33,0x22,0x38,0x8B,0xEA,0xAE,0x99,0xA0,0x90,0x82,0x00, +0x89,0xBF,0x8A,0xE8,0xA9,0x90,0x01,0x12,0x13,0x12,0x08,0xA9,0xAA,0xC9,0x22,0x63, +0x63,0x12,0x44,0x00,0x10,0x88,0x9C,0x98,0xA1,0x85,0x03,0x32,0x36,0x80,0x89,0xDB, +0xDB,0xBB,0xB9,0xBA,0x01,0x81,0x28,0x19,0xCB,0xFA,0xBC,0x09,0x13,0x37,0x34,0x34, +0x23,0x31,0x20,0x10,0x00,0x00,0x28,0x38,0x10,0x88,0xEC,0x8D,0xCB,0xBC,0xCC,0xBB, +0xBB,0xC9,0x99,0x00,0x00,0x33,0x11,0x22,0x81,0x07,0x41,0x54,0x34,0x34,0x22,0x31, +0x00,0x88,0x9A,0x9B,0x98,0xAB,0x8E,0x9B,0xBD,0x9C,0xBC,0xBB,0xDA,0xAA,0xA9,0x99, +0x18,0x38,0x60,0x20,0x31,0x13,0x13,0x51,0x14,0x31,0x53,0x33,0x35,0x22,0x01,0x8A, +0x9C,0xA9,0xCA,0xC9,0xA8,0x00,0x10,0x81,0x9C,0x9E,0xAB,0xCC,0xAB,0xBA,0x98,0x30, +0x52,0x03,0x81,0x08,0x9C,0xAC,0xAC,0x18,0x11,0x03,0x51,0x61,0x41,0x31,0x31,0x02, +0x01,0x20,0x24,0x43,0x44,0x40,0x30,0x10,0xBC,0xBE,0xCB,0xDB,0xAB,0xBA,0x99,0x98, +0x99,0xAA,0xBD,0xAA,0xC8,0x90,0x11,0x53,0x37,0x23,0x43,0x34,0x33,0x33,0x33,0x11, +0x28,0x00,0x19,0xA9,0x9A,0xCB,0xCE,0xBB,0xEB,0xBC,0xBB,0xCA,0xBA,0xA8,0x88,0x11, +0x12,0x21,0x20,0x22,0x26,0x26,0x23,0x23,0x43,0x24,0x22,0x32,0x20,0x31,0x81,0x9A, +0xBC,0xBC,0xCB,0xBD,0x9A,0xA9,0x90,0x98,0xBA,0xCC,0xCB,0xBC,0x8B,0x88,0x22,0x35, +0x23,0x12,0x99,0x8B,0xAA,0xAA,0x89,0x82,0x93,0x31,0x42,0x23,0x23,0x21,0x32,0x11, +0x20,0x13,0x13,0x24,0x24,0x24,0x22,0x11,0x8A,0x9E,0xAC,0xAC,0xAA,0xBA,0xAA,0xAB, +0xBD,0xBC,0xCB,0xCB,0xA9,0xA8,0x91,0x12,0x44,0x43,0x44,0x34,0x34,0x42,0x33,0x42, +0x21,0x11,0x11,0x88,0x80,0xAA,0x0B,0xAC,0xCB,0xEC,0xAC,0xBA,0xCA,0xAB,0x9A,0x99, +0x80,0x91,0x09,0x08,0x10,0x22,0x44,0x43,0x44,0x33,0x43,0x22,0x13,0x21,0x22,0x20, +0x09,0x88,0xB9,0xC8,0xBB,0xAB,0xAB,0xA9,0xA9,0x9B,0x9B,0x99,0x90,0x90,0x00,0x81, +0x00,0x08,0x09,0x8A,0x9A,0xAA,0xA9,0xA9,0x99,0x90,0x80,0x01,0x80,0x00,0x09,0x31, +0x32,0x44,0x33,0x43,0x34,0x33,0x24,0x22,0x23,0x12,0x10,0x09,0x9B,0xAB,0xCA,0xCC, +0xBB,0xCB,0xDA,0xCA,0xAB,0xCA,0xAB,0xA9,0xA8,0x92,0x12,0x43,0x53,0x35,0x23,0x33, +0x43,0x43,0x52,0x22,0x22,0x21,0x01,0x09,0x89,0xA9,0xBB,0xBD,0xBC,0xCB,0xDA,0xAB, +0xAB,0xAB,0xAA,0xA9,0x99,0xA8,0x09,0x01,0x11,0x34,0x25,0x23,0x33,0x51,0x22,0x31, +0x12,0x20,0x21,0x12,0x10,0x80,0x99,0x9A,0x99,0x99,0x88,0x08,0x00,0x88,0xA9,0x99, +0x99,0x80,0x80,0x10,0x01,0x00,0x9A,0xAA,0xBB,0xBA,0xBA,0xA9,0x99,0x99,0x89,0x99, +0x99,0x00,0x01,0x33,0x35,0x24,0x23,0x34,0x23,0x33,0x34,0x33,0x43,0x32,0x21,0x88, +0xAB,0xBD,0xBB,0xDB,0xAB,0xBA,0xBB,0xDA,0xBB,0xCB,0xBB,0xBC,0xA8,0x90,0x01,0x12, +0x23,0x43,0x53,0x34,0x34,0x39,0x80,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00, + +/* Source: 20RIM.ROM */ +/* Length: 128 / 0x00000080 */ + +0x0F,0xFF,0x73,0x8E,0x71,0xCD,0x00,0x49,0x10,0x90,0x21,0x49,0xA0,0xDB,0x02,0x3A, +0xE3,0x0A,0x50,0x98,0xC0,0x59,0xA2,0x99,0x09,0x22,0xA2,0x80,0x10,0xA8,0x5B,0xD2, +0x88,0x21,0x09,0x96,0xA8,0x10,0x0A,0xE0,0x08,0x48,0x19,0xAB,0x52,0xA8,0x92,0x0C, +0x03,0x19,0xE2,0x0A,0x12,0xC2,0x81,0x1E,0x01,0xD0,0x48,0x88,0x98,0x01,0x49,0x91, +0xAA,0x2C,0x25,0x89,0x88,0xB5,0x81,0xA2,0x9A,0x12,0x9E,0x38,0x3B,0x81,0x9B,0x59, +0x01,0x93,0xCA,0x4A,0x21,0xA0,0x3D,0x0A,0x39,0x3D,0x12,0xA8,0x3F,0x18,0x01,0x92, +0x1C,0x00,0xB2,0x48,0xB9,0x94,0xA3,0x19,0x4F,0x19,0xB2,0x32,0x90,0xBA,0x01,0xE6, +0x91,0x80,0xC1,0xA4,0x2A,0x08,0xA1,0xB1,0x25,0xD2,0x88,0x99,0x21,0x80,0x88,0x80, +}; diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 722dc51bb..33809d17e 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -23,6 +23,7 @@ #include #include +#include "sound/ymfm/ymfm_opn.h" #include "ym2203shared.h" #include "fmshared_OPN.h" @@ -1101,6 +1102,7 @@ int DivPlatformYM2203::init(DivEngine* p, int channels, int sugRate, unsigned in oscBuf[i]=new DivDispatchOscBuffer; } fm=new ymfm::ym2203(iface); + fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); // YM2149, 2MHz ay=new DivPlatformAY8910; ay->init(p,3,sugRate,35); diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 3b3d55767..ae32c0ebd 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -20,9 +20,11 @@ #include "ym2608.h" #include "sound/ymfm/ymfm.h" #include "../engine.h" +#include "../../ta-log.h" #include #include +#include "sound/ymfm/ymfm_opn.h" #include "ym2610shared.h" #include "fmshared_OPN.h" @@ -33,7 +35,7 @@ static unsigned char konOffs[6]={ #define CHIP_DIVIDER 32 -const char* regCheatSheetYM2610B[]={ +const char* regCheatSheetYM2608[]={ // SSG "SSG_FreqL_A", "000", "SSG_FreqH_A", "001", @@ -181,30 +183,6 @@ const char* regCheatSheetYM2610B[]={ "ADPCMA_Ch4_Vol", "10B", "ADPCMA_Ch5_Vol", "10C", "ADPCMA_Ch6_Vol", "10D", - "ADPCMA_Ch1_StL", "110", - "ADPCMA_Ch2_StL", "111", - "ADPCMA_Ch3_StL", "112", - "ADPCMA_Ch4_StL", "113", - "ADPCMA_Ch5_StL", "114", - "ADPCMA_Ch6_StL", "115", - "ADPCMA_Ch1_StH", "118", - "ADPCMA_Ch2_StH", "119", - "ADPCMA_Ch3_StH", "11A", - "ADPCMA_Ch4_StH", "11B", - "ADPCMA_Ch5_StH", "11C", - "ADPCMA_Ch6_StH", "11D", - "ADPCMA_Ch1_EdL", "120", - "ADPCMA_Ch2_EdL", "121", - "ADPCMA_Ch3_EdL", "122", - "ADPCMA_Ch4_EdL", "123", - "ADPCMA_Ch5_EdL", "124", - "ADPCMA_Ch6_EdL", "125", - "ADPCMA_Ch1_EdH", "128", - "ADPCMA_Ch2_EdH", "129", - "ADPCMA_Ch3_EdH", "12A", - "ADPCMA_Ch4_EdH", "12B", - "ADPCMA_Ch5_EdH", "12C", - "ADPCMA_Ch6_EdH", "12D", // FM (Channel 4-6) "FM4_Op1_DT_MULT", "130", "FM5_Op1_DT_MULT", "131", @@ -305,11 +283,11 @@ const char* regCheatSheetYM2610B[]={ NULL }; -const char** DivPlatformYM2610B::getRegisterSheet() { - return regCheatSheetYM2610B; +const char** DivPlatformYM2608::getRegisterSheet() { + return regCheatSheetYM2608; } -const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { +const char* DivPlatformYM2608::getEffectName(unsigned char effect) { switch (effect) { case 0x10: return "10xy: Setup LFO (x: enable; y: speed)"; @@ -429,7 +407,7 @@ const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { return NULL; } -double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { +double DivPlatformYM2608::NOTE_OPNB(int ch, int note) { if (ch>8) { // ADPCM-B return NOTE_ADPCMB(note); } else if (ch>5) { // PSG @@ -439,7 +417,7 @@ double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { return NOTE_FNUM_BLOCK(note,11); } -double DivPlatformYM2610B::NOTE_ADPCMB(int note) { +double DivPlatformYM2608::NOTE_ADPCMB(int note) { if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { double off=65535.0*(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; return parent->calcBaseFreq((double)chipClock/144,off,note,false); @@ -447,9 +425,10 @@ double DivPlatformYM2610B::NOTE_ADPCMB(int note) { return 0; } -void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { +void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; + /* ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); @@ -463,6 +442,7 @@ void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t fmChan[i]=fme->debug_channel(i); adpcmAChan[i]=aae->debug_channel(i); } + */ for (size_t h=start; hdata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); } @@ -505,10 +485,11 @@ void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t } oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + */ } } -void DivPlatformYM2610B::tick(bool sysTick) { +void DivPlatformYM2608::tick(bool sysTick) { // PSG ay->tick(sysTick); ay->flushWrites(); @@ -684,7 +665,7 @@ void DivPlatformYM2610B::tick(bool sysTick) { if (chan[15].std.vol.had) { chan[15].outVol=(chan[15].vol*MIN(64,chan[15].std.vol.val))/64; - immWrite(0x1b,chan[15].outVol); + immWrite(0x10b,chan[15].outVol); } if (chan[15].std.arp.had) { @@ -710,8 +691,8 @@ void DivPlatformYM2610B::tick(bool sysTick) { } else { chan[15].freq=0; } - immWrite(0x19,chan[15].freq&0xff); - immWrite(0x1a,(chan[15].freq>>8)&0xff); + immWrite(0x109,chan[15].freq&0xff); + immWrite(0x10a,(chan[15].freq>>8)&0xff); chan[15].freqChanged=false; } @@ -752,7 +733,7 @@ void DivPlatformYM2610B::tick(bool sysTick) { } } -int DivPlatformYM2610B::dispatch(DivCommand c) { +int DivPlatformYM2608::dispatch(DivCommand c) { if (c.chan>5 && c.chan<9) { c.chan-=6; return ay->dispatch(c); @@ -771,18 +752,19 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].macroInit(ins); if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; - immWrite(0x1b,chan[c.chan].outVol); + immWrite(0x10b,chan[c.chan].outVol); } 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); + immWrite(0x102,(s->offB>>5)&0xff); + immWrite(0x103,(s->offB>>13)&0xff); 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 + immWrite(0x104,(end>>5)&0xff); + immWrite(0x105,(end>>13)&0xff); + printf("sample want\n"); + immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); + immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -791,11 +773,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { 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); + immWrite(0x100,0x01); // reset + immWrite(0x102,0); + immWrite(0x103,0); + immWrite(0x104,0); + immWrite(0x105,0); break; } } else { @@ -803,44 +785,30 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { - immWrite(0x10,0x01); // reset - immWrite(0x12,0); - immWrite(0x13,0); - immWrite(0x14,0); - immWrite(0x15,0); + immWrite(0x100,0x01); // reset + immWrite(0x102,0); + immWrite(0x103,0); + immWrite(0x104,0); + immWrite(0x105,0); break; } DivSample* s=parent->getSample(12*sampleBank+c.value%12); - immWrite(0x12,(s->offB>>8)&0xff); - immWrite(0x13,s->offB>>16); + immWrite(0x102,(s->offB>>5)&0xff); + immWrite(0x103,(s->offB>>13)&0xff); 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 + immWrite(0x104,(end>>5)&0xff); + immWrite(0x105,(end>>13)&0xff); + immWrite(0x101,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x100,(s->loopStart>=0)?0x90:0x80); // start/repeat chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); chan[c.chan].freqChanged=true; } break; } - if (c.chan>8) { // ADPCM-A + if (c.chan>8) { // RSS TODO: improve rhythm writing strategy if (skipRegisterWrites) break; - if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { - immWrite(0x100,0x80|(1<<(c.chan-9))); - immWrite(0x110+c.chan-9,0); - immWrite(0x118+c.chan-9,0); - immWrite(0x120+c.chan-9,0); - immWrite(0x128+c.chan-9,0); - break; - } - DivSample* s=parent->getSample(12*sampleBank+c.value%12); - immWrite(0x110+c.chan-9,(s->offA>>8)&0xff); - immWrite(0x118+c.chan-9,s->offA>>16); - int end=s->offA+s->lengthA-1; - immWrite(0x120+c.chan-9,(end>>8)&0xff); - immWrite(0x128+c.chan-9,end>>16); - immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); - immWrite(0x100,0x00|(1<<(c.chan-9))); + immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + immWrite(0x10,0x00|(1<<(c.chan-9))); break; } DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); @@ -894,11 +862,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } case DIV_CMD_NOTE_OFF: if (c.chan>14) { - immWrite(0x10,0x01); // reset + immWrite(0x100,0x01); // reset break; } if (c.chan>8) { - immWrite(0x100,0x80|(1<<(c.chan-9))); + immWrite(0x10,0x80|(1<<(c.chan-9))); break; } chan[c.chan].keyOff=true; @@ -908,11 +876,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; case DIV_CMD_NOTE_OFF_ENV: if (c.chan>14) { - immWrite(0x10,0x01); // reset + immWrite(0x100,0x01); // reset break; } if (c.chan>8) { - immWrite(0x100,0x80|(1<<(c.chan-9))); + immWrite(0x10,0x80|(1<<(c.chan-9))); break; } chan[c.chan].keyOff=true; @@ -929,11 +897,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].outVol=c.value; } if (c.chan>14) { // ADPCM-B - immWrite(0x1b,chan[c.chan].outVol); + immWrite(0x10b,chan[c.chan].outVol); break; } if (c.chan>8) { // ADPCM-A - immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } for (int i=0; i<4; i++) { @@ -964,11 +932,11 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); } if (c.chan>14) { - immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x101,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); break; } if (c.chan>8) { - immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } 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)); @@ -1263,13 +1231,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { return 1; } -void DivPlatformYM2610B::muteChannel(int ch, bool mute) { +void DivPlatformYM2608::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (ch>14) { // ADPCM-B - immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6)); + //immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6)); } if (ch>8) { // ADPCM-A - immWrite(0x108+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); + immWrite(0x18+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); return; } if (ch>5) { // PSG @@ -1280,7 +1248,7 @@ void DivPlatformYM2610B::muteChannel(int ch, bool mute) { rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); } -void DivPlatformYM2610B::forceIns() { +void DivPlatformYM2608::forceIns() { for (int i=0; i<6; i++) { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; @@ -1316,31 +1284,31 @@ void DivPlatformYM2610B::forceIns() { ay->getRegisterWrites().clear(); } -void* DivPlatformYM2610B::getChanState(int ch) { +void* DivPlatformYM2608::getChanState(int ch) { return &chan[ch]; } -DivDispatchOscBuffer* DivPlatformYM2610B::getOscBuffer(int ch) { +DivDispatchOscBuffer* DivPlatformYM2608::getOscBuffer(int ch) { return oscBuf[ch]; } -unsigned char* DivPlatformYM2610B::getRegisterPool() { +unsigned char* DivPlatformYM2608::getRegisterPool() { return regPool; } -int DivPlatformYM2610B::getRegisterPoolSize() { +int DivPlatformYM2608::getRegisterPoolSize() { return 512; } -void DivPlatformYM2610B::poke(unsigned int addr, unsigned short val) { +void DivPlatformYM2608::poke(unsigned int addr, unsigned short val) { immWrite(addr,val); } -void DivPlatformYM2610B::poke(std::vector& wlist) { +void DivPlatformYM2608::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); } -void DivPlatformYM2610B::reset() { +void DivPlatformYM2608::reset() { while (!writes.empty()) writes.pop(); memset(regPool,0,512); if (dumpWrites) { @@ -1348,7 +1316,7 @@ void DivPlatformYM2610B::reset() { } fm->reset(); for (int i=0; i<16; i++) { - chan[i]=DivPlatformYM2610B::Channel(); + chan[i]=DivPlatformYM2608::Channel(); chan[i].std.setEngine(parent); } for (int i=0; i<6; i++) { @@ -1379,23 +1347,30 @@ void DivPlatformYM2610B::reset() { immWrite(0x22,0x08); // PCM volume - immWrite(0x101,0x3f); // A - immWrite(0x1b,0xff); // B + immWrite(0x11,0x3f); // A + immWrite(0x10b,0xff); // B + + // ADPCM limit + immWrite(0x10d,0xff); + immWrite(0x10c,0xff); + + // enable 6 channel mode + immWrite(0x29,0x80); ay->reset(); ay->getRegisterWrites().clear(); ay->flushWrites(); } -bool DivPlatformYM2610B::isStereo() { +bool DivPlatformYM2608::isStereo() { return true; } -bool DivPlatformYM2610B::keyOffAffectsArp(int ch) { +bool DivPlatformYM2608::keyOffAffectsArp(int ch) { return (ch>5); } -void DivPlatformYM2610B::notifyInsChange(int ins) { +void DivPlatformYM2608::notifyInsChange(int ins) { for (int i=0; i<16; i++) { if (chan[i].ins==ins) { chan[i].insChanged=true; @@ -1404,17 +1379,59 @@ void DivPlatformYM2610B::notifyInsChange(int ins) { ay->notifyInsChange(ins); } -void DivPlatformYM2610B::notifyInsDeletion(void* ins) { +void DivPlatformYM2608::notifyInsDeletion(void* ins) { ay->notifyInsDeletion(ins); } -void DivPlatformYM2610B::setSkipRegisterWrites(bool value) { +void DivPlatformYM2608::setSkipRegisterWrites(bool value) { DivDispatch::setSkipRegisterWrites(value); ay->setSkipRegisterWrites(value); } -int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { - DivPlatformYM2610Base::init(p, channels, sugRate, flags); +const void* DivPlatformYM2608::getSampleMem(int index) { + return index == 0 ? adpcmBMem : NULL; +} + +size_t DivPlatformYM2608::getSampleMemCapacity(int index) { + return index == 0 ? 2097152 : 0; +} + +size_t DivPlatformYM2608::getSampleMemUsage(int index) { + return index == 0 ? adpcmBMemLen : 0; +} + +void DivPlatformYM2608::renderSamples() { + memset(adpcmBMem,0,getSampleMemCapacity(0)); + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + int paddedLen=(s->lengthB+255)&(~0xff); + if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { + memPos=(memPos+0xfffff)&0xf00000; + } + if (memPos>=getSampleMemCapacity(0)) { + logW("out of ADPCM memory for sample %d!",i); + break; + } + if (memPos+paddedLen>=getSampleMemCapacity(0)) { + memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(0)-memPos); + logW("out of ADPCM memory for sample %d!",i); + } else { + memcpy(adpcmBMem+memPos,s->dataB,paddedLen); + } + s->offB=memPos; + memPos+=paddedLen; + } + adpcmBMemLen=memPos+256; +} + +int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + adpcmBMem=new unsigned char[getSampleMemCapacity(0)]; + adpcmBMemLen=0; + iface.adpcmBMem=adpcmBMem; + iface.sampleBank=0; dumpWrites=false; skipRegisterWrites=false; for (int i=0; i<16; i++) { @@ -1422,11 +1439,13 @@ int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned i oscBuf[i]=new DivDispatchOscBuffer; } chipClock=8000000; - rate=chipClock/16; + fm=new ymfm::ym2608(iface); + fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); + rate=fm->sample_rate(chipClock); + printf("rate: %d\n",rate); for (int i=0; i<16; i++) { oscBuf[i]->rate=rate; } - fm=new ymfm::ym2610b(iface); // YM2149, 2MHz ay=new DivPlatformAY8910; ay->init(p,3,sugRate,35); @@ -1435,15 +1454,15 @@ int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned i return 16; } -void DivPlatformYM2610B::quit() { +void DivPlatformYM2608::quit() { for (int i=0; i<16; i++) { delete oscBuf[i]; } ay->quit(); delete ay; delete fm; - DivPlatformYM2610Base::quit(); + delete[] adpcmBMem; } -DivPlatformYM2610B::~DivPlatformYM2610B() { +DivPlatformYM2608::~DivPlatformYM2608() { } diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h index 0de28f6fd..6845f0a3e 100644 --- a/src/engine/platform/ym2608.h +++ b/src/engine/platform/ym2608.h @@ -24,9 +24,18 @@ #include #include "sound/ymfm/ymfm_opn.h" -#include "ym2610.h" +#include "ay.h" -class DivPlatformYM2608: public DivPlatformYM2610Base { +class DivYM2608Interface: public ymfm::ymfm_interface { + public: + unsigned char* adpcmBMem; + int sampleBank; + uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address); + void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data); + DivYM2608Interface(): adpcmBMem(NULL), sampleBank(0) {} +}; + +class DivPlatformYM2608: public DivDispatch { protected: const unsigned short chanOffs[6]={ 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 @@ -88,6 +97,10 @@ class DivPlatformYM2608: public DivPlatformYM2610Base { ymfm::ym2608::output_data fmout; unsigned char regPool[512]; unsigned char lastBusy; + + unsigned char* adpcmBMem; + size_t adpcmBMemLen; + DivYM2608Interface iface; DivPlatformAY8910* ay; unsigned char sampleBank; @@ -123,6 +136,10 @@ class DivPlatformYM2608: public DivPlatformYM2610Base { void poke(std::vector& wlist); const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); + const void* getSampleMem(int index); + size_t getSampleMemCapacity(int index); + size_t getSampleMemUsage(int index); + void renderSamples(); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformYM2608(); diff --git a/src/engine/platform/ym2608Interface.cpp b/src/engine/platform/ym2608Interface.cpp new file mode 100644 index 000000000..62cd8899f --- /dev/null +++ b/src/engine/platform/ym2608Interface.cpp @@ -0,0 +1,42 @@ +/** + * 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 "sound/ymfm/ymfm.h" +#include "ym2608.h" +#include "../engine.h" + +#include "sound/rss.h" + +uint8_t DivYM2608Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) { + switch (type) { + case ymfm::ACCESS_ADPCM_A: + return YM2608_ADPCM_ROM[address&0x1fff]; + case ymfm::ACCESS_ADPCM_B: + if (adpcmBMem==NULL) { + return 0; + } + return adpcmBMem[address&0xffffff]; + default: + return 0; + } + return 0; +} + +void DivYM2608Interface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) { +} From db2ebc49307bcc9762456209594ec2f95540425d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 03:18:14 -0500 Subject: [PATCH 39/68] OPNA: improvements --- src/engine/engine.cpp | 8 +++++--- src/engine/platform/ym2608.cpp | 2 -- src/engine/sysDef.cpp | 5 ++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 99e41313c..320425c7f 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1612,7 +1612,9 @@ int DivEngine::addInstrument(int refChan) { *ins=song.nullInsQSound; } ins->name=fmt::sprintf("Instrument %d",insCount); - ins->type=prefType; + if (prefType!=DIV_INS_NULL) { + ins->type=prefType; + } saveLock.lock(); song.ins.push_back(ins); song.insLen=insCount+1; @@ -2310,9 +2312,9 @@ void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) { // 1. check which channels are viable for this instrument DivInstrument* insInst=getIns(ins); - if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type) notInViableChannel=true; + if (getPreferInsType(finalChan)!=insInst->type && getPreferInsSecondType(finalChan)!=insInst->type && getPreferInsType(finalChan)!=DIV_INS_NULL) notInViableChannel=true; for (int i=0; i=song.insLen || getPreferInsType(i)==insInst->type || getPreferInsSecondType(i)==insInst->type) { + if (ins==-1 || ins>=song.insLen || getPreferInsType(i)==insInst->type || (getPreferInsType(i)==DIV_INS_NULL && finalChanType==DIV_CH_NOISE) || getPreferInsSecondType(i)==insInst->type) { if (insInst->type==DIV_INS_OPL) { if (insInst->fm.ops==2 || getChannelType(i)==DIV_CH_OP) { isViable[i]=true; diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index ae32c0ebd..31ecba9c3 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -762,7 +762,6 @@ int DivPlatformYM2608::dispatch(DivCommand c) { int end=s->offB+s->lengthB-1; immWrite(0x104,(end>>5)&0xff); immWrite(0x105,(end>>13)&0xff); - printf("sample want\n"); immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat if (c.value!=DIV_NOTE_NULL) { @@ -1442,7 +1441,6 @@ int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, unsigned in fm=new ymfm::ym2608(iface); fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); rate=fm->sample_rate(chipClock); - printf("rate: %d\n",rate); for (int i=0; i<16; i++) { oscBuf[i]->rate=rate; } diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 934789890..3621c77e9 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1274,7 +1274,10 @@ void DivEngine::registerSystems() { {"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"}, {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_PULSE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM}, - {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} + {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_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_NULL, DIV_INS_AMIGA}, + {}, + fmHardResetEffectHandler, + fmPostEffectHandler ); sysDefs[DIV_SYSTEM_OPL]=new DivSysDef( From 34d868522bc3a4c68ea2eae03dcf73de0c2688b3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 03:18:29 -0500 Subject: [PATCH 40/68] update Bubble System and SegaPCM docs --- papers/doc/7-systems/bubblesystem.md | 12 +++++------- papers/doc/7-systems/segapcm.md | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/papers/doc/7-systems/bubblesystem.md b/papers/doc/7-systems/bubblesystem.md index 0c363163e..7b14e90ea 100644 --- a/papers/doc/7-systems/bubblesystem.md +++ b/papers/doc/7-systems/bubblesystem.md @@ -1,14 +1,12 @@ # Bubble System WSG -a Konami's 2 channel wavetable sound generator logic used at their arcade hardware Bubble System. +a Konami-made 2 channel wavetable sound generator logic used on the Bubble System arcade board, configured with K005289, a 4-bit PROM and DAC. -It's configured with K005289, 4 bit PROM and DAC. +however, the K005289 is just part of the logic used for pitch and wavetable ROM address. +waveform select and volume control are tied with single AY-3-8910 IO for both channels. +another AY-3-8910 IO is used for reading sound hardware status. -Also known as K005289, but that's just part of the logic used for pitch and wavetable ROM address. -Waveform select and Volume control are tied with single AY-3-8910 IO for both channels. -Another AY-3-8910 IO is used for reading sound hardware status. - -Furnace emulates this configurations as single system, waveform format is 15 level and 32 width. +Furnace emulates this configuration as single system with 32x16 wavetables. # effects diff --git a/papers/doc/7-systems/segapcm.md b/papers/doc/7-systems/segapcm.md index 1b3ed63fb..364626332 100644 --- a/papers/doc/7-systems/segapcm.md +++ b/papers/doc/7-systems/segapcm.md @@ -4,7 +4,7 @@ yep, that's right! 16 channels of PCM! -a chip used in the Sega OutRun/X/Y arcade boards. eventually the MultiPCM surpassed it with 24 channels, and later they joined the software mixing gang. +a chip used in the Sega OutRun/X/Y arcade boards. eventually the MultiPCM surpassed it with 28 channels, and later they joined the software mixing gang. # effects - `20xx`: set PCM frequency. From 4a563a416e901711348752fdd1c75bf03f15f068 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 14:59:36 -0500 Subject: [PATCH 41/68] prepare for Y8950/YMU759 ADPCM --- CMakeLists.txt | 2 +- src/engine/platform/opl.cpp | 96 ++++++++++++++++++++++++++- src/engine/platform/opl.h | 21 +++++- src/engine/platform/oplAInterface.cpp | 38 +++++++++++ 4 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 src/engine/platform/oplAInterface.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f912faea..dd2624944 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -318,8 +318,8 @@ src/engine/platform/sound/vrcvi/vrcvi.cpp src/engine/platform/sound/scc/scc.cpp +src/engine/platform/oplAInterface.cpp src/engine/platform/ym2608Interface.cpp - src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 0933934c8..8fd1ede37 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -19,6 +19,7 @@ #include "opl.h" #include "../engine.h" +#include "../../ta-log.h" #include #include @@ -246,19 +247,48 @@ const char* DivPlatformOPL::getEffectName(unsigned char effect) { void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { static short o[2]; static int os[2]; + static ymfm::ymfm_output<2> aOut; for (size_t h=start; h=0) { + adpcmB->write(w.addr-7,(w.val&15)|0x80); + OPL3_WriteReg(&fm,w.addr,w.val&0xc0); + } else { + OPL3_WriteReg(&fm,w.addr,w.val); + } + break; + case 7: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 21: case 22: case 23: + if (adpcmChan>=0) { + adpcmB->write(w.addr-7,w.val); + } else { + OPL3_WriteReg(&fm,w.addr,w.val); + } + break; + default: + OPL3_WriteReg(&fm,w.addr,w.val); + break; + } regPool[w.addr&511]=w.val; writes.pop(); } OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1]; + if (adpcmChan>=0) { + adpcmB->clock(); + aOut.clear(); + adpcmB->output<2>(aOut,0); + + os[0]+=aOut.data[0]; + os[1]+=aOut.data[1]; + } + for (int i=0; i=0) ? adpcmBMem : NULL; +} + +size_t DivPlatformOPL::getSampleMemCapacity(int index) { + return (index==0 && adpcmChan>=0) ? 2097152 : 0; +} + +size_t DivPlatformOPL::getSampleMemUsage(int index) { + return (index==0 && adpcmChan>=0) ? adpcmBMemLen : 0; +} + +void DivPlatformOPL::renderSamples() { + if (adpcmChan<0) return; + memset(adpcmBMem,0,getSampleMemCapacity(0)); + + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + int paddedLen=(s->lengthB+255)&(~0xff); + if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { + memPos=(memPos+0xfffff)&0xf00000; + } + if (memPos>=getSampleMemCapacity(0)) { + logW("out of ADPCM memory for sample %d!",i); + break; + } + if (memPos+paddedLen>=getSampleMemCapacity(0)) { + memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(0)-memPos); + logW("out of ADPCM memory for sample %d!",i); + } else { + memcpy(adpcmBMem+memPos,s->dataB,paddedLen); + } + s->offB=memPos; + memPos+=paddedLen; + } + adpcmBMemLen=memPos+256; +} + int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; @@ -1437,6 +1515,14 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f } setFlags(flags); + if (adpcmChan>=0) { + adpcmBMem=new unsigned char[getSampleMemCapacity(0)]; + adpcmBMemLen=0; + iface.adpcmBMem=adpcmBMem; + iface.sampleBank=0; + adpcmB=new ymfm::adpcm_b_engine(iface,2); + } + reset(); return totalChans; } @@ -1445,6 +1531,10 @@ void DivPlatformOPL::quit() { for (int i=0; i<18; i++) { delete oscBuf[i]; } + if (adpcmChan>=0) { + delete adpcmB; + delete[] adpcmBMem; + } } DivPlatformOPL::~DivPlatformOPL() { diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 64db4d665..c4e0215d7 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -23,6 +23,16 @@ #include "../macroInt.h" #include #include "../../../extern/opl/opl3.h" +#include "sound/ymfm/ymfm_adpcm.h" + +class DivOPLAInterface: public ymfm::ymfm_interface { + public: + unsigned char* adpcmBMem; + int sampleBank; + uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address); + void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data); + DivOPLAInterface(): adpcmBMem(NULL), sampleBank(0) {} +}; class DivPlatformOPL: public DivDispatch { protected: @@ -73,13 +83,18 @@ class DivPlatformOPL: public DivDispatch { }; std::queue writes; opl3_chip fm; + unsigned char* adpcmBMem; + size_t adpcmBMemLen; + DivOPLAInterface iface; + + ymfm::adpcm_b_engine* adpcmB; const unsigned char** slotsNonDrums; const unsigned char** slotsDrums; const unsigned char** slots; const unsigned short* chanMap; const unsigned char* outChanMap; double chipFreqBase; - int delay, oplType, chans, melodicChans, totalChans; + int delay, oplType, chans, melodicChans, totalChans, adpcmChan; unsigned char lastBusy; unsigned char drumState; unsigned char drumVol[5]; @@ -127,6 +142,10 @@ class DivPlatformOPL: public DivDispatch { void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char* getEffectName(unsigned char effect); + const void* getSampleMem(int index); + size_t getSampleMemCapacity(int index); + size_t getSampleMemUsage(int index); + void renderSamples(); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); ~DivPlatformOPL(); diff --git a/src/engine/platform/oplAInterface.cpp b/src/engine/platform/oplAInterface.cpp new file mode 100644 index 000000000..999fb47d1 --- /dev/null +++ b/src/engine/platform/oplAInterface.cpp @@ -0,0 +1,38 @@ +/** + * 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 "sound/ymfm/ymfm.h" +#include "opl.h" +#include "../engine.h" + +uint8_t DivOPLAInterface::ymfm_external_read(ymfm::access_class type, uint32_t address) { + switch (type) { + case ymfm::ACCESS_ADPCM_B: + if (adpcmBMem==NULL) { + return 0; + } + return adpcmBMem[address&0xffffff]; + default: + return 0; + } + return 0; +} + +void DivOPLAInterface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) { +} From 7218710268acefe5710339102bf66f06a1951a79 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 16:52:44 -0500 Subject: [PATCH 42/68] add Y8950 system --- src/engine/dispatchContainer.cpp | 8 ++ src/engine/platform/opl.cpp | 184 +++++++++++++++++++++++++++++-- src/engine/platform/opl.h | 10 +- src/engine/sysDef.cpp | 10 +- src/gui/guiConst.cpp | 2 + src/gui/presets.cpp | 26 +++++ 6 files changed, 225 insertions(+), 15 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 46efffc31..e74dd8225 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -273,6 +273,14 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformOPL; ((DivPlatformOPL*)dispatch)->setOPLType(3,true); break; + case DIV_SYSTEM_Y8950: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(8950,false); + break; + case DIV_SYSTEM_Y8950_DRUMS: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(8950,true); + break; case DIV_SYSTEM_OPZ: dispatch=new DivPlatformTX81Z; break; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 8fd1ede37..97a382a18 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -286,7 +286,8 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ adpcmB->output<2>(aOut,0); os[0]+=aOut.data[0]; - os[1]+=aOut.data[1]; + os[1]+=aOut.data[0]; + oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0]; } for (int i=0; i=0 && chan[adpcmChan].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0; + return parent->calcBaseFreq((double)chipClock/144,off,note,false); + } + return 0; +} + void DivPlatformOPL::tick(bool sysTick) { for (int i=0; i=0) { + if (chan[adpcmChan].furnacePCM) { + chan[adpcmChan].std.next(); + + if (chan[adpcmChan].std.vol.had) { + chan[adpcmChan].outVol=(chan[adpcmChan].vol*MIN(64,chan[adpcmChan].std.vol.val))/64; + immWrite(18,chan[adpcmChan].outVol); + } + + if (chan[adpcmChan].std.arp.had) { + if (!chan[adpcmChan].inPorta) { + if (chan[adpcmChan].std.arp.mode) { + chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].std.arp.val); + } else { + chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note+(signed char)chan[adpcmChan].std.arp.val); + } + } + chan[adpcmChan].freqChanged=true; + } else { + if (chan[adpcmChan].std.arp.mode && chan[adpcmChan].std.arp.finished) { + chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note); + chan[adpcmChan].freqChanged=true; + } + } + } + if (chan[adpcmChan].freqChanged) { + if (chan[adpcmChan].sample>=0 && chan[adpcmChan].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0; + chan[adpcmChan].freq=parent->calcFreq(chan[adpcmChan].baseFreq,chan[adpcmChan].pitch,false,4,chan[adpcmChan].pitch2,(double)chipClock/144,off); + } else { + chan[adpcmChan].freq=0; + } + immWrite(16,chan[adpcmChan].freq&0xff); + immWrite(17,(chan[adpcmChan].freq>>8)&0xff); + chan[adpcmChan].freqChanged=false; + } + } + for (int i=0; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { immWrite(i,pendingWrites[i]&0xff); @@ -655,13 +704,77 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) { } int DivPlatformOPL::dispatch(DivCommand c) { - if (c.chan>=totalChans) return 0; + if (c.chan>=totalChans && c.chan!=adpcmChan) return 0; // ineffective in 4-op mode - if (oplType==3 && c.chan<14 && (c.chan&1) && c.cmd!=DIV_CMD_GET_VOLMAX && c.cmd!=DIV_ALWAYS_SET_VOLUME) { + if (oplType==3 && c.chan!=adpcmChan && c.chan<14 && (c.chan&1) && c.cmd!=DIV_CMD_GET_VOLMAX && c.cmd!=DIV_ALWAYS_SET_VOLUME) { if (chan[c.chan-1].fourOp) return 0; } switch (c.cmd) { case DIV_CMD_NOTE_ON: { + if (c.chan==adpcmChan) { // ADPCM + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); + 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].macroInit(ins); + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + immWrite(18,chan[c.chan].outVol); + } + 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(9,(s->offB>>2)&0xff); + immWrite(10,(s->offB>>10)&0xff); + int end=s->offB+s->lengthB-1; + immWrite(11,(end>>2)&0xff); + immWrite(12,(end>>10)&0xff); + immWrite(8,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); + immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // 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(7,0x01); // reset + immWrite(9,0); + immWrite(10,0); + immWrite(11,0); + immWrite(12,0); + break; + } + } else { + chan[c.chan].sample=-1; + chan[c.chan].macroInit(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + immWrite(7,0x01); // reset + immWrite(9,0); + immWrite(10,0); + immWrite(11,0); + immWrite(12,0); + break; + } + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(9,(s->offB>>2)&0xff); + immWrite(10,(s->offB>>10)&0xff); + int end=s->offB+s->lengthB-1; + immWrite(11,(end>>2)&0xff); + immWrite(12,(end>>10)&0xff); + immWrite(8,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(7,(s->loopStart>=0)?0x90:0x80); // start/repeat + chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); + chan[c.chan].freqChanged=true; + } + break; + } DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_OPL); if (chan[c.chan].insChanged) { @@ -744,11 +857,19 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: + if (c.chan==adpcmChan) { + immWrite(7,0x01); // reset + break; + } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; break; case DIV_CMD_NOTE_OFF_ENV: + if (c.chan==adpcmChan) { + immWrite(7,0x01); // reset + break; + } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; @@ -758,7 +879,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].std.release(); break; case DIV_CMD_VOLUME: { - if (pretendYMU) { + if (pretendYMU && c.chan!=adpcmChan) { c.value=pow(((double)c.value/127.0),0.5)*63.0; if (c.value<0) c.value=0; if (c.value>63) c.value=63; @@ -767,6 +888,10 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } + if (c.chan==adpcmChan) { // ADPCM-B + immWrite(18,chan[c.chan].outVol); + break; + } int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; for (int i=0; ichan[c.chan].baseFreq) { @@ -857,13 +983,22 @@ int DivPlatformOPL::dispatch(DivCommand c) { } break; } + case DIV_CMD_SAMPLE_BANK: + if (adpcmChan<0) break; + sampleBank=c.value; + if (sampleBank>(int)(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + iface.sampleBank=sampleBank; + break; case DIV_CMD_LEGATO: { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].baseFreq=(c.chan==adpcmChan)?(NOTE_ADPCMB(c.value)):(NOTE_FREQUENCY(c.value)); chan[c.chan].note=c.value; chan[c.chan].freqChanged=true; break; } case DIV_CMD_FM_LFO: { + if (c.chan==adpcmChan) break; if (c.value&2) { dvb=c.value&1; } else { @@ -873,6 +1008,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_FB: { + if (c.chan==adpcmChan) break; chan[c.chan].state.fb=c.value&7; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (isMuted[c.chan]) { @@ -889,6 +1025,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_MULT: { + if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value>=ops) break; DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; @@ -900,6 +1037,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_TL: { + if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value>=ops) break; DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; @@ -919,6 +1057,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AR: { + if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value<0) { for (int i=0; i=0) { + chan[adpcmChan]=DivPlatformOPL::Channel(); + chan[adpcmChan].std.setEngine(parent); + chan[adpcmChan].vol=0xff; + chan[adpcmChan].outVol=0xff; + + adpcmB->reset(); + + // volume + immWrite(18,0xff); + // ADPCM limit + immWrite(20,0xff); + immWrite(19,0xff); + } if (oplType<3) for (int i=0; i=0) ? 2097152 : 0; + return (index==0 && adpcmChan>=0) ? 262144 : 0; } size_t DivPlatformOPL::getSampleMemUsage(int index) { diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index c4e0215d7..a67d50380 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -40,8 +40,8 @@ class DivPlatformOPL: public DivDispatch { DivInstrumentFM state; DivMacroInt std; unsigned char freqH, freqL; - int freq, baseFreq, pitch, pitch2, note, ins; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, fourOp, hardReset; + int freq, baseFreq, pitch, pitch2, note, ins, sample; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnacePCM, inPorta, fourOp, hardReset; int vol, outVol; unsigned char pan; void macroInit(DivInstrument* which) { @@ -57,13 +57,14 @@ class DivPlatformOPL: public DivDispatch { pitch2(0), note(0), ins(-1), + sample(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), - furnaceDac(false), + furnacePCM(false), inPorta(false), fourOp(false), hardReset(false), @@ -94,7 +95,7 @@ class DivPlatformOPL: public DivDispatch { const unsigned short* chanMap; const unsigned char* outChanMap; double chipFreqBase; - int delay, oplType, chans, melodicChans, totalChans, adpcmChan; + int delay, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank; unsigned char lastBusy; unsigned char drumState; unsigned char drumVol[5]; @@ -112,6 +113,7 @@ class DivPlatformOPL: public DivDispatch { int octave(int freq); int toFreq(int freq); + double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3621c77e9..85e5eb9cc 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1736,7 +1736,10 @@ void DivEngine::registerSystems() { {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "PCM"}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "PCM"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM}, - {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_AMIGA} + {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_AMIGA}, + {}, + oplEffectHandler, + fmOPLPostEffectHandler ); sysDefs[DIV_SYSTEM_Y8950_DRUMS]=new DivSysDef( @@ -1744,7 +1747,10 @@ void DivEngine::registerSystems() { {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat", "PCM"}, {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "PCM"}, {DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM}, - {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_AMIGA} + {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_AMIGA}, + {}, + oplEffectHandler, + fmOPLPostEffectHandler ); sysDefs[DIV_SYSTEM_SCC_PLUS]=new DivSysDef( diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 4ea2de0c2..88e9c7c26 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -841,6 +841,8 @@ const int availableSystems[]={ DIV_SYSTEM_VRC7, DIV_SYSTEM_OPL, DIV_SYSTEM_OPL_DRUMS, + DIV_SYSTEM_Y8950, + DIV_SYSTEM_Y8950_DRUMS, DIV_SYSTEM_OPL2, DIV_SYSTEM_OPL2_DRUMS, DIV_SYSTEM_OPL3, diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index dd003803a..65ad3a2f3 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -106,6 +106,18 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha Y8950", { + DIV_SYSTEM_Y8950, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha Y8950 (drums mode)", { + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Yamaha YM3812", { DIV_SYSTEM_OPL2, 64, 0, 0, @@ -411,6 +423,20 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + MSX-AUDIO", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_Y8950, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + MSX-AUDIO (drums mode)", { + DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_Y8950_DRUMS, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "MSX + MSX-MUSIC", { DIV_SYSTEM_AY8910, 64, 0, 16, From 315018ff24a0140434ae584cfeba400f3f5a450d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 17:22:00 -0500 Subject: [PATCH 43/68] YMU759: ADPCM --- src/engine/platform/opl.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 97a382a18..5ba5342f6 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -568,6 +568,7 @@ void DivPlatformOPL::tick(bool sysTick) { } } if (chan[adpcmChan].freqChanged) { + printf("freq changed\n"); if (chan[adpcmChan].sample>=0 && chan[adpcmChan].samplesong.sampleLen) { double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0; chan[adpcmChan].freq=parent->calcFreq(chan[adpcmChan].baseFreq,chan[adpcmChan].pitch,false,4,chan[adpcmChan].pitch2,(double)chipClock/144,off); @@ -733,7 +734,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { int end=s->offB+s->lengthB-1; immWrite(11,(end>>2)&0xff); immWrite(12,(end>>10)&0xff); - immWrite(8,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); + immWrite(8,2); immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; @@ -768,10 +769,11 @@ int DivPlatformOPL::dispatch(DivCommand c) { int end=s->offB+s->lengthB-1; immWrite(11,(end>>2)&0xff); immWrite(12,(end>>10)&0xff); - immWrite(8,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(7,(s->loopStart>=0)?0x90:0x80); // start/repeat - chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); - chan[c.chan].freqChanged=true; + immWrite(8,2); + immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat + int freq=(65536.0*(double)s->rate)/(double)rate; + immWrite(16,freq&0xff); + immWrite(17,(freq>>8)&0xff); } break; } @@ -947,6 +949,9 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_PITCH: { + if (c.chan==adpcmChan) { + if (!chan[c.chan].furnacePCM) break; + } chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; From 1ffbfe17d458733773ea141579f9a9a8f3e39b4e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 17:40:05 -0500 Subject: [PATCH 44/68] Y8950/YMU759: ADPCM muting --- src/engine/platform/opl.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 5ba5342f6..fe8fd4397 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -285,9 +285,13 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ aOut.clear(); adpcmB->output<2>(aOut,0); - os[0]+=aOut.data[0]; - os[1]+=aOut.data[0]; - oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0]; + if (!isMuted[adpcmChan]) { + os[0]+=aOut.data[0]; + os[1]+=aOut.data[0]; + oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0]; + } else { + oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0; + } } for (int i=0; i Date: Fri, 13 May 2022 17:41:01 -0500 Subject: [PATCH 45/68] fix loading of pre-0.4 .dmf modules --- src/engine/fileOps.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index e0f207948..99b46319d 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -654,7 +654,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } for (int j=0; j0x05) { // current pattern format + if (ds.version>0x08) { // current pattern format for (int k=0; kdata[k][0]=reader.readS(); @@ -741,11 +741,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { unsigned char vol=reader.readC(); unsigned char fx=reader.readC(); unsigned char fxVal=reader.readC(); - pat->data[k][3]=(vol==0x80)?-1:vol; + pat->data[k][3]=(vol==0x80 || vol==0xff)?-1:vol; // effect - pat->data[k][4]=(fx==0x80)?-1:fx; - pat->data[k][5]=(fxVal==0x80)?-1:fxVal; - // instrument wasn't stored back then + pat->data[k][4]=(fx==0x80 || fx==0xff)?-1:fx; + pat->data[k][5]=(fxVal==0x80 || fx==0xff)?-1:fxVal; + // instrument + if (ds.version>0x05) { + pat->data[k][2]=reader.readC(); + if (pat->data[k][2]==0x80 || pat->data[k][2]==0xff) pat->data[k][2]=-1; + } } } } From 9936c66184ba9d391b3982040085e8f59e52e0dc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 17:41:13 -0500 Subject: [PATCH 46/68] OPL: remove printf --- src/engine/platform/opl.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index fe8fd4397..20b95c9b9 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -572,7 +572,6 @@ void DivPlatformOPL::tick(bool sysTick) { } } if (chan[adpcmChan].freqChanged) { - printf("freq changed\n"); if (chan[adpcmChan].sample>=0 && chan[adpcmChan].samplesong.sampleLen) { double off=65535.0*(double)(parent->getSample(chan[adpcmChan].sample)->centerRate)/8363.0; chan[adpcmChan].freq=parent->calcFreq(chan[adpcmChan].baseFreq,chan[adpcmChan].pitch,false,4,chan[adpcmChan].pitch2,(double)chipClock/144,off); From be0cbfa1f767d8e844d6075b595040abcff1e514 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 18:03:35 -0500 Subject: [PATCH 47/68] GUI: fix non-spaced MML string parsing fixes #457 --- src/gui/gui.cpp | 80 +++++++++++-------------------------------------- src/gui/gui.h | 2 -- 2 files changed, 18 insertions(+), 64 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index abeb569d4..e424de045 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -194,21 +194,6 @@ void FurnaceGUI::decodeKeyMap(std::map& map, String source) { } } -void FurnaceGUI::encodeMMLStr(String& target, unsigned char* macro, unsigned char macroLen, signed char macroLoop, signed char macroRel) { - target=""; - char buf[32]; - for (int i=0; imacroMax) macro[macroLen]=macroMax; - macroLen++; - buf=0; - } - break; - case '|': - if (macroLoop==-1) { - macroLoop=macroLen; - } - break; - case '/': - if (macroRel==-1) { - macroRel=macroLen; - } - break; - } - if (macroLen>=128) break; - } - if (hasVal && macroLen<128) { - hasVal=false; - macro[macroLen]=buf; - if (macro[macroLen]macroMax) macro[macroLen]=macroMax; - macroLen++; - buf=0; - } -} - void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel) { int buf=0; bool negaBuf=false; @@ -370,11 +308,29 @@ void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLe } break; case '|': + if (hasVal) { + hasVal=false; + macro[macroLen]=negaBuf?-buf:buf; + negaBuf=false; + if (macro[macroLen]macroMax) macro[macroLen]=macroMax; + macroLen++; + buf=0; + } if (macroLoop==-1) { macroLoop=macroLen; } break; case '/': + if (hasVal) { + hasVal=false; + macro[macroLen]=negaBuf?-buf:buf; + negaBuf=false; + if (macro[macroLen]macroMax) macro[macroLen]=macroMax; + macroLen++; + buf=0; + } if (macroRel==-1) { macroRel=macroLen; } diff --git a/src/gui/gui.h b/src/gui/gui.h index 61b2d912f..4017dccaa 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1318,8 +1318,6 @@ class FurnaceGUI { void initSystemPresets(); void encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex=false); - void encodeMMLStr(String& target, unsigned char* macro, unsigned char macroLen, signed char macroLoop, signed char macroRel); - void decodeMMLStr(String& source, unsigned char* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel); void decodeMMLStr(String& source, int* macro, unsigned char& macroLen, signed char& macroLoop, int macroMin, int macroMax, signed char& macroRel); void decodeMMLStrW(String& source, int* macro, int& macroLen, int macroMax, bool hex=false); From 77481f23cb128230c9aea7be03444a1608d36ada Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 18:46:52 -0500 Subject: [PATCH 48/68] prepare to implement sample map --- src/engine/instrument.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 47fc95ddc..18cefd017 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -311,6 +311,32 @@ struct DivInstrumentAmiga { int noteFreq[120]; short noteMap[120]; + /** + * get the sample at specified note. + * @return the sample. + */ + inline short getSample(int note) { + if (useNoteMap) { + if (note<0) note=0; + if (note>119) note=119; + return noteMap[note]; + } + return initSample; + } + + /** + * get the sample frequency at specified note. + * @return the frequency, or -1 if not using note map. + */ + inline int getFreq(int note) { + if (useNoteMap) { + if (note<0) note=0; + if (note>119) note=119; + return noteFreq[note]; + } + return -1; + } + DivInstrumentAmiga(): initSample(0), useNoteMap(false), From bf11e85bff650fcaee45241a3e453b9e935c1231 Mon Sep 17 00:00:00 2001 From: LovelyA72 <29054052+LovelyA72@users.noreply.github.com> Date: Fri, 13 May 2022 21:52:33 -0400 Subject: [PATCH 49/68] Enable loading of common Chinese characters --- src/gui/gui.h | 2 ++ src/gui/settings.cpp | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/gui/gui.h b/src/gui/gui.h index 4017dccaa..957ee16ae 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -867,6 +867,7 @@ class FurnaceGUI { int roundedButtons; int roundedMenus; int loadJapanese; + int loadChinese; int fmLayout; int sampleLayout; int waveLayout; @@ -955,6 +956,7 @@ class FurnaceGUI { roundedButtons(1), roundedMenus(0), loadJapanese(0), + loadChinese(0), fmLayout(0), sampleLayout(0), waveLayout(0), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index f44c2aa92..741705aee 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -978,6 +978,19 @@ void FurnaceGUI::drawSettings() { ); } + bool loadChineseB=settings.loadChinese; + if (ImGui::Checkbox("Display Chinese (Simplified) characters",&loadChineseB)) { + settings.loadChinese=loadChineseB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Only toggle this option if you have enough graphics memory.\n" + "This is a temporary solution until dynamic font atlas is implemented in Dear ImGui.\n\n" + "请在确保你有足够的显存后再启动此设定\n" + "这是一个在ImGui实现动态字体加载之前的临时解决方案" + ); + } + ImGui::Separator(); ImGui::Text("Orders row number format:"); @@ -1855,6 +1868,7 @@ void FurnaceGUI::syncSettings() { settings.roundedButtons=e->getConfInt("roundedButtons",1); settings.roundedMenus=e->getConfInt("roundedMenus",0); settings.loadJapanese=e->getConfInt("loadJapanese",0); + settings.loadChinese=e->getConfInt("loadChinese",0); settings.fmLayout=e->getConfInt("fmLayout",0); settings.sampleLayout=e->getConfInt("sampleLayout",0); settings.waveLayout=e->getConfInt("waveLayout",0); @@ -1931,6 +1945,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.roundedButtons,0,1); clampSetting(settings.roundedMenus,0,1); clampSetting(settings.loadJapanese,0,1); + clampSetting(settings.loadChinese,0,1); clampSetting(settings.fmLayout,0,3); clampSetting(settings.susPosition,0,1); clampSetting(settings.effectCursorDir,0,2); @@ -2048,6 +2063,7 @@ void FurnaceGUI::commitSettings() { e->setConf("roundedButtons",settings.roundedButtons); e->setConf("roundedMenus",settings.roundedMenus); e->setConf("loadJapanese",settings.loadJapanese); + e->setConf("loadChinese",settings.loadChinese); e->setConf("fmLayout",settings.fmLayout); e->setConf("sampleLayout",settings.sampleLayout); e->setConf("waveLayout",settings.waveLayout); @@ -2638,6 +2654,9 @@ void FurnaceGUI::applyUISettings(bool updateFonts) { if (settings.loadJapanese) { range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesJapanese()); } + if (settings.loadChinese) { + range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon()); + } // I'm terribly sorry range.UsedChars[0x80>>5]=0; From 14e0189993448db643491916db0d11ddc13c63da Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 21:44:34 -0500 Subject: [PATCH 50/68] WonderSwan: fix forceIns() messing with noise --- src/engine/platform/swan.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index b089394a7..22c35511d 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -448,6 +448,7 @@ void DivPlatformSwan::muteChannel(int ch, bool mute) { } void DivPlatformSwan::forceIns() { + noise=0; for (int i=0; i<4; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; From d7ec3b13c5db274b7dfde0385311c74d118bf8fd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 22:43:00 -0500 Subject: [PATCH 51/68] ADPCM-B: fixes to compatible mode --- src/engine/platform/ym2608.cpp | 11 +++++++---- src/engine/platform/ym2610.cpp | 7 +++++-- src/engine/platform/ym2610b.cpp | 7 +++++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 31ecba9c3..ec41a8c64 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -797,10 +797,11 @@ int DivPlatformYM2608::dispatch(DivCommand c) { int end=s->offB+s->lengthB-1; immWrite(0x104,(end>>5)&0xff); immWrite(0x105,(end>>13)&0xff); - immWrite(0x101,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x100,(s->loopStart>=0)?0x90:0x80); // start/repeat - chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); - chan[c.chan].freqChanged=true; + immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); + immWrite(0x100,(s->loopStart>=0)?0xb0:0xa0); // start/repeat + int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); + immWrite(0x109,freq&0xff); + immWrite(0x10a,(freq>>8)&0xff); } break; } @@ -942,6 +943,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { break; } case DIV_CMD_PITCH: { + if (c.chan==15 && !chan[c.chan].furnacePCM) break; chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; @@ -1022,6 +1024,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { + if (c.chan==15 && !chan[c.chan].furnacePCM) break; chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); chan[c.chan].freqChanged=true; break; diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 397f6a8f1..ba29263b6 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -840,8 +840,9 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat - chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); - chan[c.chan].freqChanged=true; + int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); + immWrite(0x19,freq&0xff); + immWrite(0x1a,(freq>>8)&0xff); } break; } @@ -997,6 +998,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_PITCH: { + if (c.chan==13 && !chan[c.chan].furnacePCM) break; chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; @@ -1077,6 +1079,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { + if (c.chan==13 && !chan[c.chan].furnacePCM) break; chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); chan[c.chan].freqChanged=true; break; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index cd40fe139..b5afce5e2 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -818,8 +818,9 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat - chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); - chan[c.chan].freqChanged=true; + int freq=(65536.0*(double)s->rate)/((double)chipClock/144.0); + immWrite(0x19,freq&0xff); + immWrite(0x1a,(freq>>8)&0xff); } break; } @@ -975,6 +976,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } case DIV_CMD_PITCH: { + if (c.chan==15 && !chan[c.chan].furnacePCM) break; chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; @@ -1055,6 +1057,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { + if (c.chan==15 && !chan[c.chan].furnacePCM) break; chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); chan[c.chan].freqChanged=true; break; From 07871eacc3967f89d101316097168ceaca04ec4c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 22:55:17 -0500 Subject: [PATCH 52/68] Amiga: linear pitch oopsie --- src/engine/platform/amiga.cpp | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index df742d352..c665b9165 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -220,7 +220,7 @@ void DivPlatformAmiga::tick(bool sysTick) { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA); - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); + chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOn) { @@ -238,7 +238,6 @@ int DivPlatformAmiga::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); - double off=1.0; if (ins->amiga.useWave) { chan[c.chan].useWave=true; chan[c.chan].audLen=(ins->amiga.waveLen+1)>>1; @@ -252,17 +251,9 @@ int DivPlatformAmiga::dispatch(DivCommand c) { } else { chan[c.chan].sample=ins->amiga.initSample; chan[c.chan].useWave=false; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[c.chan].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=8363.0/(double)s->centerRate; - } - } } if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value)); + chan[c.chan].baseFreq=round(NOTE_PERIODIC_NOROUND(c.value)); } if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { chan[c.chan].sample=-1; @@ -329,16 +320,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { case DIV_CMD_NOTE_PORTA: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); chan[c.chan].sample=ins->amiga.initSample; - double off=1.0; - if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[c.chan].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=8363.0/(double)s->centerRate; - } - } - int destFreq=round(off*NOTE_PERIODIC_NOROUND(c.value2)); + int destFreq=round(NOTE_PERIODIC_NOROUND(c.value2)); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -361,16 +343,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: { - double off=1.0; - if (!chan[c.chan].useWave && chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { - DivSample* s=parent->getSample(chan[c.chan].sample); - if (s->centerRate<1) { - off=1.0; - } else { - off=8363.0/(double)s->centerRate; - } - } - chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)))); + chan[c.chan].baseFreq=round(NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; From 0a0ddfa661f5e7cdfd8f59cf8806621c5827c508 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 13 May 2022 23:04:40 -0500 Subject: [PATCH 53/68] partially bring up sample map --- src/engine/platform/amiga.cpp | 4 ++-- src/engine/platform/genesis.cpp | 2 +- src/engine/platform/mmc5.cpp | 2 +- src/engine/platform/nes.cpp | 2 +- src/engine/platform/opl.cpp | 2 +- src/engine/platform/pce.cpp | 2 +- src/engine/platform/qsound.cpp | 2 +- src/engine/platform/segapcm.cpp | 2 +- src/engine/platform/su.cpp | 5 +++-- src/engine/platform/swan.cpp | 2 +- src/engine/platform/vera.cpp | 2 +- src/engine/platform/vrc6.cpp | 2 +- src/engine/platform/x1_010.cpp | 2 +- src/engine/platform/ym2608.cpp | 2 +- src/engine/platform/ym2610.cpp | 2 +- src/engine/platform/ym2610b.cpp | 2 +- 16 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index c665b9165..e93e3419e 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -249,7 +249,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { } } } else { - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); chan[c.chan].useWave=false; } if (c.value!=DIV_NOTE_NULL) { @@ -319,7 +319,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { break; case DIV_CMD_NOTE_PORTA: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value2); int destFreq=round(NOTE_PERIODIC_NOROUND(c.value2)); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index b35d09eef..a8d5c5258 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -547,7 +547,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (c.chan==5 && dacMode) { if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA) { // Furnace mode - dacSample=ins->amiga.initSample; + dacSample=ins->amiga.getSample(c.value); if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 9775e6397..abfdfd571 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -192,7 +192,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { if (c.chan==2) { // PCM DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD); if (ins->type==DIV_INS_AMIGA) { - dacSample=ins->amiga.initSample; + dacSample=ins->amiga.getSample(c.value); if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index ef3b51f06..427fdb423 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -381,7 +381,7 @@ int DivPlatformNES::dispatch(DivCommand c) { if (c.chan==4) { // PCM DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD); if (ins->type==DIV_INS_AMIGA) { - dacSample=ins->amiga.initSample; + dacSample=ins->amiga.getSample(c.value); if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 20b95c9b9..949d104b8 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -730,7 +730,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(18,chan[c.chan].outVol); } - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(9,(s->offB>>2)&0xff); diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 9838f987f..775cdc1a4 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -270,7 +270,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { if (chan[c.chan].pcm) { if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA) { - chan[c.chan].dacSample=ins->amiga.initSample; + chan[c.chan].dacSample=ins->amiga.getSample(c.value); if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { chan[c.chan].dacSample=-1; if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index e9b4e8a48..c07e07141 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -390,7 +390,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value); } diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 346daeae1..a16f0e161 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -162,7 +162,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA) { - chan[c.chan].pcm.sample=ins->amiga.initSample; + chan[c.chan].pcm.sample=ins->amiga.getSample(c.value); if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { chan[c.chan].pcm.sample=-1; if (dumpWrites) { diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 4af605547..724fb5901 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -186,7 +186,8 @@ void DivPlatformSoundUnit::tick(bool sysTick) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].pcm) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); - DivSample* sample=parent->getSample(ins->amiga.initSample); + // TODO: sample map? + DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note)); if (sample!=NULL) { double off=0.25; if (sample->centerRate<1) { @@ -204,7 +205,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) { if (chan[i].keyOn) { if (chan[i].pcm) { DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); - DivSample* sample=parent->getSample(ins->amiga.initSample); + DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note)); if (sample!=NULL) { unsigned int sampleEnd=sample->offSU+sample->samples; if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1; diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 22c35511d..dbec54593 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -263,7 +263,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { dacPos=0; dacPeriod=0; if (ins->type==DIV_INS_AMIGA) { - dacSample=ins->amiga.initSample; + dacSample=ins->amiga.getSample(c.value); if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 65a7452ee..52554f3cc 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -260,7 +260,7 @@ int DivPlatformVERA::dispatch(DivCommand c) { if (c.chan<16) { rWriteLo(c.chan,2,chan[c.chan].vol); } else { - chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.initSample; + chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.getSample(c.value); if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) { chan[16].pcm.sample=-1; } diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 4e8117942..ba3815a62 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -243,7 +243,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { if (chan[c.chan].pcm) { if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA) { - chan[c.chan].dacSample=ins->amiga.initSample; + chan[c.chan].dacSample=ins->amiga.getSample(c.value); if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { chan[c.chan].dacSample=-1; if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index f1711243c..c161b56b3 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -559,7 +559,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { if (chan[c.chan].furnacePCM) { chan[c.chan].pcm=true; chan[c.chan].macroInit(ins); - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); 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); diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index ec41a8c64..a6855550c 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -754,7 +754,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x10b,chan[c.chan].outVol); } - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(0x102,(s->offB>>5)&0xff); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index ba29263b6..0797110c7 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -795,7 +795,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x1b,chan[c.chan].outVol); } - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); 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); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index b5afce5e2..c9d29736c 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -773,7 +773,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x1b,chan[c.chan].outVol); } - chan[c.chan].sample=ins->amiga.initSample; + chan[c.chan].sample=ins->amiga.getSample(c.value); 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); From 895765b68ad99637e25eeeec8bcbc6905119e92d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 00:22:23 -0500 Subject: [PATCH 54/68] and here's your ZX beeper no real hardware yet specs of this dispatch may change at any time to fit a driver --- CMakeLists.txt | 1 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/zxbeeper.cpp | 332 +++++++++++++++++++++++++++++++ src/engine/platform/zxbeeper.h | 108 ++++++++++ src/engine/sysDef.cpp | 2 +- src/gui/guiConst.cpp | 3 +- src/gui/insEdit.cpp | 6 +- 7 files changed, 453 insertions(+), 3 deletions(-) create mode 100644 src/engine/platform/zxbeeper.cpp create mode 100644 src/engine/platform/zxbeeper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dd2624944..52cef63c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -374,6 +374,7 @@ src/engine/platform/lynx.cpp src/engine/platform/su.cpp src/engine/platform/swan.cpp src/engine/platform/vera.cpp +src/engine/platform/zxbeeper.cpp src/engine/platform/bubsyswsg.cpp src/engine/platform/n163.cpp src/engine/platform/pet.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index e74dd8225..d175b2539 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -59,6 +59,7 @@ #include "platform/scc.h" #include "platform/dummy.h" #include "../ta-log.h" +#include "platform/zxbeeper.h" #include "song.h" void DivDispatchContainer::setRates(double gotRate) { @@ -294,6 +295,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_PCSPKR: dispatch=new DivPlatformPCSpeaker; break; + case DIV_SYSTEM_SFX_BEEPER: + dispatch=new DivPlatformZXBeeper; + break; case DIV_SYSTEM_LYNX: dispatch=new DivPlatformLynx; break; diff --git a/src/engine/platform/zxbeeper.cpp b/src/engine/platform/zxbeeper.cpp new file mode 100644 index 000000000..086ff0051 --- /dev/null +++ b/src/engine/platform/zxbeeper.cpp @@ -0,0 +1,332 @@ +/** + * 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 "zxbeeper.h" +#include "../engine.h" +#include + +#define CHIP_FREQBASE 8192*6 + +const char** DivPlatformZXBeeper::getRegisterSheet() { + return NULL; +} + +const char* DivPlatformZXBeeper::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + case 0x11: + return "11xx: Toggle noise mode"; + break; + case 0x12: + return "12xx: Setup LFO (0: disabled; 1: 1x depth; 2: 16x depth; 3: 256x depth)"; + break; + case 0x13: + return "13xx: Set LFO speed"; + break; + case 0x17: + return "17xx: Toggle PCM mode"; + break; + } + return NULL; +} + +void DivPlatformZXBeeper::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; hchan[curChan].sPosition) { + if (!isMuted[curChan] && chan[curChan].outVol) sOffTimer+=chan[curChan].duty; + } + if (++curChan>=6) curChan=0; + + bufL[h]=o?16384:0; + } +} + +void DivPlatformZXBeeper::tick(bool sysTick) { + for (int i=0; i<6; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=((chan[i].vol&1)*MIN(1,chan[i].std.vol.val)); + } + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val; + chan[i].freqChanged=true; + } + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); + } else { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-2048,2048); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + if (chan[i].active) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE); + if (chan[i].freq>65535) chan[i].freq=65535; + } + if (chan[i].keyOn) { + //rWrite(16+i*5,0x80); + //chWrite(i,0x04,0x80|chan[i].vol); + } + if (chan[i].keyOff) { + chan[i].freq=0; + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformZXBeeper::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + chan[c.chan].insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + chan[c.chan].pcm=false; + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(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; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.vol.has) { + 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_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(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_STD_NOISE_MODE: + chan[c.chan].duty=c.value; + break; + case DIV_CMD_SAMPLE_MODE: + chan[c.chan].pcm=c.value; + 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_LEGATO: + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(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].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 1; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformZXBeeper::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformZXBeeper::forceIns() { + for (int i=0; i<6; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + } +} + +void* DivPlatformZXBeeper::getChanState(int ch) { + return &chan[ch]; +} + +DivDispatchOscBuffer* DivPlatformZXBeeper::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformZXBeeper::getRegisterPool() { + return regPool; +} + +int DivPlatformZXBeeper::getRegisterPoolSize() { + return 112; +} + +void DivPlatformZXBeeper::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,128); + for (int i=0; i<6; i++) { + chan[i]=DivPlatformZXBeeper::Channel(); + chan[i].std.setEngine(parent); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + lastPan=0xff; + memset(tempL,0,32*sizeof(int)); + memset(tempR,0,32*sizeof(int)); + cycles=0; + curChan=0; + sOffTimer=0; + sampleBank=0; +} + +bool DivPlatformZXBeeper::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformZXBeeper::notifyWaveChange(int wave) { +} + +void DivPlatformZXBeeper::notifyInsDeletion(void* ins) { + for (int i=0; i<6; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformZXBeeper::setFlags(unsigned int flags) { + if (flags&1) { // technically there is no PAL PC Engine but oh well... + chipClock=COLOR_PAL*4.0/5.0; + } else { + chipClock=COLOR_NTSC; + } + rate=chipClock/4; + for (int i=0; i<6; i++) { + oscBuf[i]->rate=rate; + } +} + +void DivPlatformZXBeeper::poke(unsigned int addr, unsigned short val) { + +} + +void DivPlatformZXBeeper::poke(std::vector& wlist) { + +} + +int DivPlatformZXBeeper::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<6; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + setFlags(flags); + reset(); + return 6; +} + +void DivPlatformZXBeeper::quit() { + for (int i=0; i<6; i++) { + delete oscBuf[i]; + } +} + +DivPlatformZXBeeper::~DivPlatformZXBeeper() { +} diff --git a/src/engine/platform/zxbeeper.h b/src/engine/platform/zxbeeper.h new file mode 100644 index 000000000..3a8b9ac2c --- /dev/null +++ b/src/engine/platform/zxbeeper.h @@ -0,0 +1,108 @@ +/** + * 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 _ZXBEEPER_H +#define _ZXBEEPER_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" + +class DivPlatformZXBeeper: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, pitch2, note; + int dacPeriod, dacRate; + unsigned int dacPos; + int dacSample, ins; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac; + signed char vol, outVol; + unsigned short sPosition; + unsigned char duty; + DivMacroInt std; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + note(0), + dacPeriod(0), + dacRate(0), + dacPos(0), + dacSample(-1), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + noise(false), + pcm(false), + furnaceDac(false), + vol(1), + outVol(1), + sPosition(0), + duty(3) {} + }; + Channel chan[6]; + DivDispatchOscBuffer* oscBuf[6]; + bool isMuted[6]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + unsigned char lastPan; + + int cycles, curChan, sOffTimer, delay; + int tempL[32]; + int tempR[32]; + unsigned char sampleBank, lfoMode, lfoSpeed; + unsigned char regPool[128]; + 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); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + 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(); + ~DivPlatformZXBeeper(); +}; + +#endif diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 85e5eb9cc..a83e88a73 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1326,7 +1326,7 @@ void DivEngine::registerSystems() { {"Square"}, {"SQ"}, {DIV_CH_PULSE}, - {DIV_INS_STD} + {DIV_INS_BEEPER} ); sysDefs[DIV_SYSTEM_POKEY]=new DivSysDef( diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 88e9c7c26..a3474dce0 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -101,7 +101,7 @@ const char* insTypes[DIV_INS_MAX]={ "Konami SCC/Bubble System WSG", "FM (OPZ)", "POKEY", - "PC Beeper", + "Beeper", "WonderSwan", "Atari Lynx", "VERA", @@ -831,6 +831,7 @@ const int availableSystems[]={ DIV_SYSTEM_AY8910, DIV_SYSTEM_AMIGA, DIV_SYSTEM_PCSPKR, + DIV_SYSTEM_SFX_BEEPER, DIV_SYSTEM_YMU759, DIV_SYSTEM_DUMMY, DIV_SYSTEM_SOUND_UNIT, diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 0080f1da2..5cfbd9765 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2874,7 +2874,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_GB) { volMax=0; } - if (ins->type==DIV_INS_PET) { + if (ins->type==DIV_INS_PET || ins->type==DIV_INS_BEEPER) { volMax=1; } if (ins->type==DIV_INS_FDS) { @@ -2906,6 +2906,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty/Int"; dutyMax=10; } + if (ins->type==DIV_INS_BEEPER) { + dutyLabel="Pulse Width"; + dutyMax=255; + } if (ins->type==DIV_INS_AY8930) { dutyMax=255; } From cb63921747acedfbc64eb6667829c1fa001f4479 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 00:31:14 -0500 Subject: [PATCH 55/68] ZX beeper: set a more audible duty --- TODO.md | 4 +--- src/engine/platform/zxbeeper.h | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index 52eae1713..45d042792 100644 --- a/TODO.md +++ b/TODO.md @@ -5,9 +5,7 @@ - input pad - settings - RF5C68 system -- ZX beeper system -- Y8950 system -- maybe YMU759 ADPCM channel +- ZX beeper system overlay percussion - ADPCM chips - Game Boy envelope macro/sequence - rewrite the system name detection function anyway diff --git a/src/engine/platform/zxbeeper.h b/src/engine/platform/zxbeeper.h index 3a8b9ac2c..a91b518b0 100644 --- a/src/engine/platform/zxbeeper.h +++ b/src/engine/platform/zxbeeper.h @@ -62,7 +62,7 @@ class DivPlatformZXBeeper: public DivDispatch { vol(1), outVol(1), sPosition(0), - duty(3) {} + duty(64) {} }; Channel chan[6]; DivDispatchOscBuffer* oscBuf[6]; From c685b60a3cd4464e24aa881bed13a064e94abb0b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 00:36:45 -0500 Subject: [PATCH 56/68] ZX beeper: "register" view --- src/engine/platform/zxbeeper.cpp | 6 ++++-- src/engine/platform/zxbeeper.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/zxbeeper.cpp b/src/engine/platform/zxbeeper.cpp index 086ff0051..145cfe5b5 100644 --- a/src/engine/platform/zxbeeper.cpp +++ b/src/engine/platform/zxbeeper.cpp @@ -250,11 +250,12 @@ DivDispatchOscBuffer* DivPlatformZXBeeper::getOscBuffer(int ch) { } unsigned char* DivPlatformZXBeeper::getRegisterPool() { - return regPool; + ulaOut=sOffTimer?0x10:0x08; + return &ulaOut; } int DivPlatformZXBeeper::getRegisterPoolSize() { - return 112; + return 1; } void DivPlatformZXBeeper::reset() { @@ -274,6 +275,7 @@ void DivPlatformZXBeeper::reset() { curChan=0; sOffTimer=0; sampleBank=0; + ulaOut=0; } bool DivPlatformZXBeeper::keyOffAffectsArp(int ch) { diff --git a/src/engine/platform/zxbeeper.h b/src/engine/platform/zxbeeper.h index a91b518b0..4e5feabfc 100644 --- a/src/engine/platform/zxbeeper.h +++ b/src/engine/platform/zxbeeper.h @@ -73,7 +73,7 @@ class DivPlatformZXBeeper: public DivDispatch { QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} }; std::queue writes; - unsigned char lastPan; + unsigned char lastPan, ulaOut; int cycles, curChan, sOffTimer, delay; int tempL[32]; From 031a0fc1339b04744334d9bf13f6d0f36e02f81c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 01:02:28 -0500 Subject: [PATCH 57/68] OPNA: RSS write optimization --- src/engine/platform/ym2608.cpp | 20 ++++++++++++++++---- src/engine/platform/ym2608.h | 1 + 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index a6855550c..f610467ab 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -696,6 +696,11 @@ void DivPlatformYM2608::tick(bool sysTick) { chan[15].freqChanged=false; } + if (writeRSSOff) { + immWrite(0x10,0x80|writeRSSOff); + writeRSSOff=0; + } + for (int i=16; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { immWrite(i,pendingWrites[i]&0xff); @@ -731,6 +736,11 @@ void DivPlatformYM2608::tick(bool sysTick) { chan[i].keyOn=false; } } + + if (writeRSSOn) { + immWrite(0x10,writeRSSOn); + writeRSSOn=0; + } } int DivPlatformYM2608::dispatch(DivCommand c) { @@ -805,10 +815,10 @@ int DivPlatformYM2608::dispatch(DivCommand c) { } break; } - if (c.chan>8) { // RSS TODO: improve rhythm writing strategy + if (c.chan>8) { // RSS if (skipRegisterWrites) break; + writeRSSOn|=(1<<(c.chan-9)); immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); - immWrite(0x10,0x00|(1<<(c.chan-9))); break; } DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); @@ -866,7 +876,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { break; } if (c.chan>8) { - immWrite(0x10,0x80|(1<<(c.chan-9))); + writeRSSOff|=1<<(c.chan-9); break; } chan[c.chan].keyOff=true; @@ -880,7 +890,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { break; } if (c.chan>8) { - immWrite(0x10,0x80|(1<<(c.chan-9))); + writeRSSOff|=1<<(c.chan-9); break; } chan[c.chan].keyOff=true; @@ -1340,6 +1350,8 @@ void DivPlatformYM2608::reset() { lastBusy=60; sampleBank=0; + writeRSSOff=0; + writeRSSOn=0; delay=0; diff --git a/src/engine/platform/ym2608.h b/src/engine/platform/ym2608.h index 6845f0a3e..5c6736295 100644 --- a/src/engine/platform/ym2608.h +++ b/src/engine/platform/ym2608.h @@ -104,6 +104,7 @@ class DivPlatformYM2608: public DivDispatch { DivPlatformAY8910* ay; unsigned char sampleBank; + unsigned char writeRSSOff, writeRSSOn; int delay; From ae5dcd3746d925afe7a0caa9637a531c44273513 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 01:05:05 -0500 Subject: [PATCH 58/68] OPNA: proper ADPCM muting --- src/engine/platform/ym2608.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index f610467ab..5e058dab3 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -817,7 +817,9 @@ int DivPlatformYM2608::dispatch(DivCommand c) { } if (c.chan>8) { // RSS if (skipRegisterWrites) break; - writeRSSOn|=(1<<(c.chan-9)); + if (!isMuted[c.chan]) { + writeRSSOn|=(1<<(c.chan-9)); + } immWrite(0x18+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } @@ -942,7 +944,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1); } if (c.chan>14) { - immWrite(0x101,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x101,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|2); break; } if (c.chan>8) { @@ -1246,7 +1248,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) { void DivPlatformYM2608::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (ch>14) { // ADPCM-B - //immWrite(0x11,isMuted[ch]?0:(chan[ch].pan<<6)); + immWrite(0x101,(isMuted[ch]?0:(chan[ch].pan<<6))|2); } if (ch>8) { // ADPCM-A immWrite(0x18+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); From 06e00ab17b661312577be5a2368203c213699a54 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 01:07:32 -0500 Subject: [PATCH 59/68] OPNA: per-channel oscilloscope --- src/engine/platform/sound/ymfm/ymfm_opn.h | 6 ++++++ src/engine/platform/ym2608.cpp | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/sound/ymfm/ymfm_opn.h b/src/engine/platform/sound/ymfm/ymfm_opn.h index 1b47885b9..7fc76dac5 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opn.h +++ b/src/engine/platform/sound/ymfm/ymfm_opn.h @@ -546,6 +546,12 @@ public: // generate one sample of sound void generate(output_data *output, uint32_t numsamples = 1); + // get the engine + fm_engine* debug_fm_engine() { return &m_fm; } + ssg_engine* debug_ssg_engine() { return &m_ssg; } + adpcm_a_engine* debug_adpcm_a_engine() { return &m_adpcm_a; } + adpcm_b_engine* debug_adpcm_b_engine() { return &m_adpcm_b; } + protected: // internal helpers void update_prescale(uint8_t prescale); diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 5e058dab3..4f05fc01c 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -428,7 +428,6 @@ double DivPlatformYM2608::NOTE_ADPCMB(int note) { void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; - /* ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); @@ -442,7 +441,6 @@ void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t l fmChan[i]=fme->debug_channel(i); adpcmAChan[i]=aae->debug_channel(i); } - */ for (size_t h=start; hdata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); } @@ -485,7 +482,6 @@ void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t l } oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); - */ } } From 6288b485fc8b7c7e26d5e5e61925618b9407c9d1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 01:09:24 -0500 Subject: [PATCH 60/68] oops --- src/engine/platform/ym2608.cpp | 2 +- src/engine/platform/ym2610.cpp | 2 +- src/engine/platform/ym2610b.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 4f05fc01c..236e6a087 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -428,7 +428,7 @@ double DivPlatformYM2608::NOTE_ADPCMB(int note) { void DivPlatformYM2608::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; - ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ym2608::fm_engine* fme=fm->debug_fm_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 0797110c7..ce3e4b7c1 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -470,7 +470,7 @@ double DivPlatformYM2610::NOTE_ADPCMB(int note) { void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; - ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ym2610::fm_engine* fme=fm->debug_fm_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index c9d29736c..5a36aaaf4 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -450,7 +450,7 @@ double DivPlatformYM2610B::NOTE_ADPCMB(int note) { void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; - ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); + ymfm::ym2610b::fm_engine* fme=fm->debug_fm_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); From 297ab01d4c236779b281d8be74bac2cf33edd748 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 01:12:23 -0500 Subject: [PATCH 61/68] OPN: per-channel oscilloscope --- src/engine/platform/sound/ymfm/ymfm_opn.h | 2 ++ src/engine/platform/ym2203.cpp | 31 ++++++----------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/engine/platform/sound/ymfm/ymfm_opn.h b/src/engine/platform/sound/ymfm/ymfm_opn.h index 7fc76dac5..74d2b01dd 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opn.h +++ b/src/engine/platform/sound/ymfm/ymfm_opn.h @@ -465,6 +465,8 @@ public: // generate one sample of sound void generate(output_data *output, uint32_t numsamples = 1); + fm_engine* debug_fm_engine() { return &m_fm; } + protected: // internal helpers void update_prescale(uint8_t prescale); diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index 33809d17e..10fde1f29 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -284,19 +284,12 @@ const char* DivPlatformYM2203::getEffectName(unsigned char effect) { void DivPlatformYM2203::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os; - /*ymfm::ym2612::fm_engine* fme=fm->debug_fm_engine(); - ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); - ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); - ymfm::adpcm_b_engine* abe=fm->debug_adpcm_b_engine(); + ymfm::ym2203::fm_engine* fme=fm->debug_fm_engine(); - ymfm::ssg_engine::output_data ssgOut; - - ymfm::fm_channel>* fmChan[6]; - ymfm::adpcm_a_channel* adpcmAChan[6]; - for (int i=0; i<6; i++) { + ymfm::fm_channel>* fmChan[3]; + for (int i=0; i<3; i++) { fmChan[i]=fme->debug_channel(i); - adpcmAChan[i]=aae->debug_channel(i); - }*/ + } for (size_t h=start; hdata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); } - ssge->get_last_out(ssgOut); - for (int i=6; i<9; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]; + for (int i=3; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]; } - - for (int i=9; i<15; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1); - } - - oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); - */ } } From 1ba5ac67661387523bf738fc9d0af8a8f037a5ff Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 14 May 2022 01:46:06 -0500 Subject: [PATCH 62/68] GUI: add ability to select entire row when clicking on row number --- TODO.md | 1 - src/gui/cursor.cpp | 49 ++++++++++++++++++++++++++++++++------------- src/gui/gui.cpp | 3 ++- src/gui/gui.h | 8 ++++---- src/gui/pattern.cpp | 15 ++++++++++++-- 5 files changed, 54 insertions(+), 22 deletions(-) diff --git a/TODO.md b/TODO.md index 45d042792..34f7d1d7d 100644 --- a/TODO.md +++ b/TODO.md @@ -16,7 +16,6 @@ - try to find out why does VSlider not accept keyboard input - finish lock layout - if macros have release, note off should release them -- add ability to select entire row when clicking on row number - store edit/followOrders/followPattern state in config - add ability to select a column by double clicking - add ability to move selection by dragging diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp index 6bf4e76ff..972beee02 100644 --- a/src/gui/cursor.cpp +++ b/src/gui/cursor.cpp @@ -21,28 +21,48 @@ #include "actionUtil.h" -void FurnaceGUI::startSelection(int xCoarse, int xFine, int y) { +void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) { + DETERMINE_FIRST_LAST; + if (xCoarse!=selStart.xCoarse || xFine!=selStart.xFine || y!=selStart.y) { curNibble=false; } - cursor.xCoarse=xCoarse; - cursor.xFine=xFine; - cursor.y=y; - selStart.xCoarse=xCoarse; - selStart.xFine=xFine; - selStart.y=y; - selEnd.xCoarse=xCoarse; - selEnd.xFine=xFine; - selEnd.y=y; + + if (fullRow) { + selStart.xCoarse=firstChannel; + selStart.xFine=0; + selEnd.xCoarse=lastChannel-1; + selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selStart.y=y; + selEnd.y=y; + } else { + cursor.xCoarse=xCoarse; + cursor.xFine=xFine; + cursor.y=y; + selStart.xCoarse=xCoarse; + selStart.xFine=xFine; + selStart.y=y; + selEnd.xCoarse=xCoarse; + selEnd.xFine=xFine; + selEnd.y=y; + } selecting=true; + selectingFull=fullRow; e->setMidiBaseChan(cursor.xCoarse); } -void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y) { +void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y, bool fullRow) { if (!selecting) return; - selEnd.xCoarse=xCoarse; - selEnd.xFine=xFine; - selEnd.y=y; + if (selectingFull) { + DETERMINE_LAST; + selEnd.xCoarse=lastChannel-1; + selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selEnd.y=y; + } else { + selEnd.xCoarse=xCoarse; + selEnd.xFine=xFine; + selEnd.y=y; + } } void FurnaceGUI::finishSelection() { @@ -66,6 +86,7 @@ void FurnaceGUI::finishSelection() { selEnd.xFine^=selStart.xFine; } selecting=false; + selectingFull=false; // boundary check int chanCount=e->getTotalChannelCount(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e424de045..4f5a5e92f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2350,7 +2350,7 @@ bool FurnaceGUI::loop() { } sampleDragActive=false; if (selecting) { - cursor=selEnd; + if (!selectingFull) cursor=selEnd; finishSelection(); demandScrollX=true; if (cursor.xCoarse==selStart.xCoarse && cursor.xFine==selStart.xFine && cursor.y==selStart.y && @@ -3975,6 +3975,7 @@ FurnaceGUI::FurnaceGUI(): chanOscDocked(false), */ selecting(false), + selectingFull(false), curNibble(false), orderNibble(false), followOrders(true), diff --git a/src/gui/gui.h b/src/gui/gui.h index 957ee16ae..182f04085 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1016,7 +1016,7 @@ class FurnaceGUI { */ SelectionPoint selStart, selEnd, cursor; - bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; + bool selecting, selectingFull, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; FurnaceGUIWindows curWindow, nextWindow, curWindowLast; float peak[2]; @@ -1121,7 +1121,7 @@ class FurnaceGUI { ImVec2 patWindowPos, patWindowSize; // pattern view specific - ImVec2 threeChars, twoChars; + ImVec2 fourChars, threeChars, twoChars; SelectionPoint sel1, sel2; int dummyRows, demandX; int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax; @@ -1262,8 +1262,8 @@ class FurnaceGUI { void commitSettings(); void processDrags(int dragX, int dragY); - void startSelection(int xCoarse, int xFine, int y); - void updateSelection(int xCoarse, int xFine, int y); + void startSelection(int xCoarse, int xFine, int y, bool fullRow=false); + void updateSelection(int xCoarse, int xFine, int y, bool fullRow=false); void finishSelection(); void moveCursor(int x, int y, bool select); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index d284b08fc..ae9bedb2c 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -88,11 +88,21 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int } } // row number + ImGui::PushStyleColor(ImGuiCol_Text,rowIndexColor); + if (settings.patRowsBase==1) { - ImGui::TextColored(rowIndexColor," %.2X ",i); + snprintf(id,31," %.2X ##PR_%d",i,i); } else { - ImGui::TextColored(rowIndexColor,"%3d ",i); + snprintf(id,31,"%3d ##PR_%d",i,i); } + ImGui::Selectable(id,false,ImGuiSelectableFlags_NoPadWithHalfSpacing,fourChars); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) { + updateSelection(0,0,i,true); + } + if (ImGui::IsItemClicked()) { + startSelection(0,0,i,true); + } + ImGui::PopStyleColor(); // for each column for (int j=0; j