From a3fc76c984405f177bdef2dfd78cc4f8cb760e7b Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 10 May 2022 11:18:25 +0700 Subject: [PATCH 01/16] 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 f6b807dd..db0a51a5 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 f44ed0d5..c04276ea 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 00000000..87a42afe --- /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 00000000..58059532 --- /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 00000000..db74d3f0 --- /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 00000000..a4580bad --- /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 6760acb6..792f16d5 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 3dd24e0a..28a57f86 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/16] 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 8005ee0d..edde54f8 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/16] Remove SCC from TODO.md --- TODO.md | 1 - 1 file changed, 1 deletion(-) diff --git a/TODO.md b/TODO.md index c11d4755..fc526b29 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 960a8e36165970444a8e1d86aa38add4b4862b9c Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 10 May 2022 17:36:09 +0700 Subject: [PATCH 04/16] 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 87a42afe..f23240e1 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 05/16] 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 f23240e1..87a42afe 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 a4580bad..e696c403 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 06/16] 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 c10aea38..64aa78c3 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 52d14777..1c22755a 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 07/16] 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 83bc56b8..7465c885 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 9ac26d89..4fcc5f1b 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 4b4771a3..2136efeb 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 08/16] 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 13ef95c7..293c4cd0 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 09/16] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1eac106d..caa4714c 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 10/16] 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 7465c885..2ea91ecd 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 3c9304a0..f0c35ecc 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 11/16] 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 2ea91ecd..fa68cd60 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 12/16] 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 46a66fe0..397f6a8f 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 03d8f3a2..cd40fe13 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 ac991ffb..440dce6a 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 98273bd9..b12fd3de 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 13/16] 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 0c19ea54..58aa55b9 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 14/16] 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 35226f50..f1711243 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 15/16] 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 b95b42ed..0933934c 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 89b41317..50eab344 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 b662b1e3..ec5f4f6d 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 4630f90e..4af60554 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 16/16] 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 489b744b..4c88794d 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),