From 79902f472f2350e404d0081d71cc73f4ecd31817 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Thu, 22 Jan 2026 18:15:06 +0700 Subject: [PATCH] Add SAXotone system --- CMakeLists.txt | 1 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/saxotone.cpp | 513 +++++++++++++++++++++++++++++++ src/engine/platform/saxotone.h | 92 ++++++ src/engine/sysDef.cpp | 35 ++- src/engine/sysDef.h | 3 +- src/gui/guiConst.cpp | 3 +- src/gui/sysConf.cpp | 72 ++++- 8 files changed, 705 insertions(+), 18 deletions(-) create mode 100644 src/engine/platform/saxotone.cpp create mode 100644 src/engine/platform/saxotone.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cdeefe36..60463c231 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -885,6 +885,7 @@ src/engine/platform/bifurcator.cpp src/engine/platform/sid2.cpp src/engine/platform/sid3.cpp src/engine/platform/multipcm.cpp +src/engine/platform/saxotone.cpp src/engine/platform/pcmdac.cpp src/engine/platform/dummy.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 6ed30ac43..3133873a8 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -94,6 +94,7 @@ #include "platform/sid2.h" #include "platform/sid3.h" #include "platform/multipcm.h" +#include "platform/saxotone.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -788,6 +789,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_MULTIPCM: dispatch=new DivPlatformMultiPCM; break; + case DIV_SYSTEM_SAXOTONE: + dispatch=new DivPlatformSAXotone; + break; case DIV_SYSTEM_DUMMY: dispatch=new DivPlatformDummy; break; diff --git a/src/engine/platform/saxotone.cpp b/src/engine/platform/saxotone.cpp new file mode 100644 index 000000000..5e4eecf28 --- /dev/null +++ b/src/engine/platform/saxotone.cpp @@ -0,0 +1,513 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2026 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 "saxotone.h" +#include "../engine.h" +#include "../waveSynth.h" +#include + +#define rWrite(a,v) regPool[a]=v +#define ADC(a,b) ((a)+(b)+(((unsigned int)(a)&255)+((unsigned int)(b)&255)>=256?1:0)) + +const char** DivPlatformSAXotone::getRegisterSheet() { + return NULL; +} + +void DivPlatformSAXotone::acquire(short** buf, size_t len) { + for (int i=0; i<5; i++) { + oscBuf[i]->begin(len); + } + + unsigned short widthMask=(waveWidth<<8)-1; + for (size_t h=0; h=6 && (outputClock&1)==0)) { + lastOut=0; + for (int i=0; i<4; i++) { + unsigned short freq=(unsigned short)regPool[0+i*4]+((unsigned short)regPool[1+i*4]<<8); + unsigned short off=(unsigned short)regPool[2+i*4]+((unsigned short)regPool[3+i*4]<<8); + chan[i].sPosition=(chan[i].sPosition+freq)&widthMask; + chan[i].out=sampleMem[(off&0xfff)+(chan[i].sPosition>>8)]; + oscBuf[i]->putSample(h,(short)chan[i].out<<8); + lastOut=ADC(lastOut,chan[i].out); + } + if ((chan[4].sPosition>>8)<(regPool[16]&15)) { + chan[4].sPosition++; + } + chan[4].out=sampleMem[chan[4].sPosition]; + oscBuf[4]->putSample(h,(short)chan[4].out<<8); + lastOut=ADC(lastOut,chan[4].out); + } + // quantize to 4 bits and clamp + short out=lastOut/8; + if (out>7) out=7; + else if (out<-8) out=-8; + buf[0][h]=out*2048; + outputClock=(outputClock+1)%(isPal?312:262); + } + + for (int i=0; i<5; i++) { + oscBuf[i]->end(len); + } +} + +void DivPlatformSAXotone::tick(bool sysTick) { + for (int i=0; i<4; i++) { + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=chan[i].vol && chan[i].std.vol.val; + writeOutVol(i); + } + if (chan[i].std.wave.had) { + chan[i].wave=chan[i].std.wave.val; + writeOutVol(i); + } + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); + } + 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,-65535,65535); + } 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,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE); + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>((int)waveWidth<<7)) chan[i].freq=(int)waveWidth<<7; + rWrite(0+i*4,chan[i].freq&0xff); + rWrite(1+i*4,chan[i].freq>>8); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformSAXotone::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + if (c.chan<4) { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_POKEMINI); + 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; + if (!parent->song.compatFlags.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + writeOutVol(c.chan); + } + } else { + // TODO support offset commands + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); + if (c.value!=DIV_NOTE_NULL) { + curSample=ins->amiga.getSample(c.value); + chan[c.chan].sampleNote=c.value; + chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote; + chan[c.chan].note=c.value; + } else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) { + curSample=ins->amiga.getSample(chan[c.chan].sampleNote); + } + if (curSample>=0) { + chan[c.chan].sPosition=sampleOff[curSample]; + rWrite(16,(sampleEnd[curSample]>>8)|0xf0); + } + 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].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + writeOutVol(c.chan); + 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; + writeOutVol(c.chan); + } + } + 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+chan[c.chan].sampleNoteDelta); + 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_WAVE: + chan[c.chan].wave=c.value; + break; + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+chan[c.chan].sampleNoteDelta+((HACKY_LEGATO_MESS)?(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.compatFlags.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POKEMINI)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.compatFlags.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 1; + break; + case DIV_CMD_MACRO_OFF: + chan[c.chan].std.mask(c.value,true); + break; + case DIV_CMD_MACRO_ON: + chan[c.chan].std.mask(c.value,false); + break; + case DIV_CMD_MACRO_RESTART: + chan[c.chan].std.restart(c.value); + break; + default: + break; + } + return 1; +} + +void DivPlatformSAXotone::writeOutVol(int ch) { + if (ch>=4) return; + if (ch==4 && (isMuted[4] || !chan[4].active)) { + chan[4].sPosition=0; + rWrite(16,0xf0); + } + bool on=!isMuted[ch] && chan[ch].active && chan[ch].outVol!=0; + int offset=on?wtOff[chan[ch].wave]:0; + rWrite(2+ch*4,offset&0xff); + rWrite(3+ch*4,(offset>>8)|0xf0); +} + +void DivPlatformSAXotone::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + writeOutVol(ch); +} + +void DivPlatformSAXotone::forceIns() { + for (int i=0; i<5; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + } +} + +void* DivPlatformSAXotone::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformSAXotone::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +DivDispatchOscBuffer* DivPlatformSAXotone::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformSAXotone::getRegisterPool() { + return regPool; +} + +int DivPlatformSAXotone::getRegisterPoolSize() { + return 17; +} + +const void* DivPlatformSAXotone::getSampleMem(int index) { + return index == 0 ? sampleMem : NULL; +} + +size_t DivPlatformSAXotone::getSampleMemCapacity(int index) { + return index == 0 ? 4086 : 0; // excluding CPU vectors and bank switch code +} + +size_t DivPlatformSAXotone::getSampleMemUsage(int index) { + return index == 0 ? sampleMemLen : 0; +} + +size_t DivPlatformSAXotone::getSampleMemOffset(int index) { + return index == 0 ? waveWidth : 0; +} + +bool DivPlatformSAXotone::isSampleLoaded(int index, int sample) { + if (index!=0) return false; + if (sample<0 || sample>256) return false; + return sampleLoaded[sample]; +} + +const DivMemoryComposition* DivPlatformSAXotone::getMemCompo(int index) { + if (index!=0) return NULL; + return &memCompo; +} + +void DivPlatformSAXotone::renderSamples(int sysID) { + memset(sampleMem,0,getSampleMemCapacity()); + memset(wtOff,0,sizeof(wtOff)); + memset(sampleOff,0,sizeof(sampleOff)); + memset(sampleEnd,0,sizeof(sampleEnd)); + memset(sampleLoaded,0,sizeof(sampleLoaded)); + + memCompo=DivMemoryComposition(); + memCompo.name="Sample Memory"; + unsigned int usedRegions[514]; + usedRegions[0]=0; + usedRegions[1]=waveWidth; + int urPosW=2; + + // allocate samples first + size_t memPos=waveWidth; // reserve for silent sample + for (int i=0; isong.sampleLen,256); i++) { + DivSample* s=parent->song.sample[i]; + if (!s->renderOn[0][sysID] || s->length8==0) { + continue; + } + + // every samples must end at address $xx00 in ROM (inclusive!) + size_t length=s->length8; + size_t endPos=((memPos+length)+0xff)&~0xff; + size_t startPos=endPos-length; + if (endPos>=getSampleMemCapacity()) { + logW("out of SAXotone memory for sample %d!",i); + break; + } + if ((memPos&0xff)>startPos) { + startPos+=0x100; + endPos+=0x100; + } + for (size_t j=0; jdata8[j]*pcmVol/256; + } + sampleMem[endPos]=0; // insert blank sample where it parks + sampleOff[i]=startPos; + sampleEnd[i]=endPos; + sampleLoaded[i]=true; + endPos++; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,startPos,endPos)); + usedRegions[urPosW++]=startPos; + usedRegions[urPosW++]=endPos; + memPos=endPos; + } + usedRegions[urPosW]=getSampleMemCapacity(); + + signed char buf[256]; + DivWaveSynth ws; + DivInstrument ins; + ws.setEngine(parent); + ws.init(&ins,waveWidth,255); + for (int i=0; isong.waveLen,256); i++) { + ws.changeWave1(i,true); + for (size_t j=0; j=usedRegions[urPosR+1] || startPos+waveWidth>usedRegions[urPosR+2])) { + urPosR+=2; + startPos=usedRegions[urPosR]; + moving=true; + } + if (urPosR>=urPosW) break; + // no page crossing + if (0x100-(startPos&0xff)=urPosW) break; + size_t cmpLen=MIN(waveWidth,memPos-startPos); + cmpLen=MIN(cmpLen,usedRegions[urPosR+1]-startPos); + if (memcmp(sampleMem+startPos,buf,cmpLen)==0) { + usedRegions[urPosR+1]=MAX(startPos+waveWidth,usedRegions[urPosR+1]); + overlap=true; + break; + } + startPos++; + } + if (!overlap) { + // no overlaps found, try searching if it can fit entirely in an unused region first + startPos=memPos; + urPosR=1; + while (urPosR=waveWidth && ((~newStartPos)&0xff)>=waveWidth) { + startPos=newStartPos; + usedRegions[urPosR]+=waveWidth; + break; + } + urPosR+=2; + } + } + if (startPos+waveWidth>=getSampleMemCapacity()) { + logW("out of SAXotone memory for wavetable %d!",i); + break; + } + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_WAVE_STATIC,fmt::sprintf("Wavetable %d",i),i,startPos,startPos+waveWidth)); + memcpy(sampleMem+startPos,buf,waveWidth); + wtOff[i]=startPos; + memPos=MAX(memPos,startPos+waveWidth); + } + sampleMemLen=memPos; + + memCompo.used=sampleMemLen; + memCompo.capacity=4092; +} + +void DivPlatformSAXotone::reset() { + memset(regPool,0,17); + for (int i=0; i<5; i++) { + chan[i]=DivPlatformSAXotone::Channel(); + chan[i].std.setEngine(parent); + if (i<4) rWrite(3+i*4,0xf0); + } + rWrite(16,0xf0); + curSample=-1; + outputClock=0; + lastOut=0; +} + +bool DivPlatformSAXotone::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformSAXotone::notifyWaveChange(int wave) { + renderSamples(0); +} + +void DivPlatformSAXotone::notifyInsDeletion(void* ins) { + for (int i=0; i<5; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformSAXotone::setFlags(const DivConfig& flags) { + isPal=flags.getInt("clockSel",0); + waveVol=flags.getInt("waveVol",32); + pcmVol=flags.getInt("pcmVol",128); + switch(flags.getInt("waveWidth",1)) { + case 0: waveWidth=16; break; + case 2: waveWidth=64; break; + case 3: waveWidth=128; break; + case 4: waveWidth=256; break; + default: waveWidth=32; break; + } + CHIP_FREQBASE=32*76*waveWidth; + if (isPal) { + chipClock=COLOR_PAL*4.0/15.0; + } else { + chipClock=COLOR_NTSC/3.0; + } + CHECK_CUSTOM_CLOCK; + rate=chipClock/76; // line rate + for (int i=0; i<5; i++) { + oscBuf[i]->setRate(rate); + } +} + +void DivPlatformSAXotone::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformSAXotone::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformSAXotone::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + for (int i=0; i<5; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + sampleMem=new signed char[4092]; + setFlags(flags); + reset(); + return 5; +} + +void DivPlatformSAXotone::quit() { + for (int i=0; i<5; i++) { + delete oscBuf[i]; + } + delete sampleMem; +} + +DivPlatformSAXotone::~DivPlatformSAXotone() { +} diff --git a/src/engine/platform/saxotone.h b/src/engine/platform/saxotone.h new file mode 100644 index 000000000..3eed22c78 --- /dev/null +++ b/src/engine/platform/saxotone.h @@ -0,0 +1,92 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2026 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 _SAXOTONE_H +#define _SAXOTONE_H + +#include "../dispatch.h" + +class DivPlatformSAXotone: public DivDispatch { + struct Channel: public SharedChannel { + unsigned short sPosition; + unsigned char wave; + signed char out; + Channel(): + SharedChannel(1), + sPosition(0), + wave(0), + out(0) {} + }; + Channel chan[5]; + DivDispatchOscBuffer* oscBuf[5]; + signed char lastOut; + bool isMuted[5]; + unsigned int wtOff[256]; + unsigned int sampleOff[256]; + unsigned int sampleEnd[256]; + bool sampleLoaded[256]; + + bool isPal; + int waveVol, pcmVol; + int curSample; + unsigned int CHIP_FREQBASE; + size_t waveWidth; + unsigned int outputClock; + unsigned char regPool[17]; + signed char* sampleMem; + size_t sampleMemLen; + DivMemoryComposition memCompo; + + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short** buf, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); + 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(const DivConfig& flags); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const void* getSampleMem(int index=0); + size_t getSampleMemCapacity(int index=0); + size_t getSampleMemUsage(int index=0); + size_t getSampleMemOffset(int index = 0); + bool isSampleLoaded(int index, int sample); + const DivMemoryComposition* getMemCompo(int index); + void renderSamples(int chipID); + const char** getRegisterSheet(); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + ~DivPlatformSAXotone(); + private: + void writeOutVol(int ch); +}; + +#endif diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index a17d942b3..3bf0ed6c0 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -311,7 +311,7 @@ const char* DivEngine::getChannelName(int chan) { if (chan<0 || chan>song.chans) return "??"; if (!curSubSong->chanName[chan].empty()) return curSubSong->chanName[chan].c_str(); if (song.dispatchChanOfChan[chan]<0) return "??"; - + return song.chanDef[chan].name.c_str(); } @@ -319,7 +319,7 @@ const char* DivEngine::getChannelShortName(int chan) { if (chan<0 || chan>song.chans) return "??"; if (!curSubSong->chanShortName[chan].empty()) return curSubSong->chanShortName[chan].c_str(); if (song.dispatchChanOfChan[chan]<0) return "??"; - + return song.chanDef[chan].shortName.c_str(); } @@ -2528,7 +2528,7 @@ void DivEngine::registerSystems() { sysDefs[DIV_SYSTEM_ESFM]=new DivSysDef( _("ESS ES1xxx series (ESFM)"), NULL, 0xd1, 0, 18, 18, 18, - true, false, 0, false, 0, 0, 0, + true, false, 0, false, 0, 0, 0, _("a unique FM synth featured in PC sound cards.\nbased on the OPL3 design, but with lots of its features extended."), DivChanDefFunc([](unsigned short ch) -> DivChanDef { return DivChanDef( @@ -2543,10 +2543,10 @@ void DivEngine::registerSystems() { }, fmESFMPostEffectHandlerMap ); - + sysDefs[DIV_SYSTEM_POWERNOISE]=new DivSysDef( _("PowerNoise"), NULL, 0xd4, 0, 4, 4, 4, - false, false, 0, false, 0, 0, 0, + false, false, 0, false, 0, 0, 0, _("a fantasy sound chip designed by jvsTSX and The Beesh-Spweesh!\nused in the Hexheld fantasy console."), DivChanDefFunc({ DivChanDef(_("Noise 1"), "N1", DIV_CH_NOISE, DIV_INS_POWERNOISE), @@ -2585,7 +2585,7 @@ void DivEngine::registerSystems() { {0x16, {DIV_CMD_DAVE_CLOCK_DIV, _("16xx: Set clock divider (0: /2; 1: /3)")}}, } ); - + sysDefs[DIV_SYSTEM_GBA_DMA]=new DivSysDef( _("Game Boy Advance DMA Sound"), NULL, 0xd7, 0, 2, 2, 2, false, true, 0, false, 1U<), - {}, + {}, SID2PostEffectHandlerMap ); - sysDefs[DIV_SYSTEM_SID3]=new DivSysDef( + sysDefs[DIV_SYSTEM_SID3]=new DivSysDef( _("SID3"), NULL, 0xf5, 0, 7, 7, 7, false, true, 0, false, (1U<256) interruptSimCycles=256; altered=true; } rightClickable - + if (altered) { e->lockSave([&]() { flags.set("clockSel",clockSel); @@ -762,7 +762,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl altered=true; } popWarningColor(); - + ImGui::Text(_("- 0 disables envelope reset. not recommended!\n- 1 may trigger SID envelope bugs.\n- values that are too high may result in notes being skipped.")); if (ImGui::Checkbox(_("Disable 1Exy env update (compatibility)"),&no1EUpdate)) { @@ -1634,7 +1634,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl altered=true; } ImGui::Unindent(); - + int chipClock=flags.getInt("customClock",0); if (!chipClock) { switch (clockSel) { @@ -2097,7 +2097,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl if (ImGui::Checkbox(_("Enable echo"),&echo)) { altered=true; } - + ImGui::Text(_("Initial echo state:")); for (int i=0; i<8; i++) { bool echoChan=(bool)(echoMask&(1<128) waveVol=128; + altered=true; + mustRender=true; + } rightClickable + ImGui::Text(_("PCM volume:")); + if (CWSliderInt("##pcmVol",&pcmVol,0,128)) { + if (pcmVol<0) pcmVol=0; + if (pcmVol>128) pcmVol=128; + altered=true; + mustRender=true; + } rightClickable + ImGui::Text(_("Wave width:")); + ImGui::Indent(); + if (ImGui::RadioButton("16",waveWidth==0)) { + waveWidth=0; + altered=true; + mustRender=true; + } + if (ImGui::RadioButton("32",waveWidth==1)) { + waveWidth=1; + altered=true; + mustRender=true; + } + if (ImGui::RadioButton("64",waveWidth==2)) { + waveWidth=2; + altered=true; + mustRender=true; + } + if (ImGui::RadioButton("128",waveWidth==3)) { + waveWidth=3; + altered=true; + mustRender=true; + } + if (ImGui::RadioButton("256",waveWidth==4)) { + waveWidth=4; + altered=true; + mustRender=true; + } + ImGui::Unindent(); + if (altered) { + e->lockSave([&]() { + flags.set("clockSel",(int)sysPal); + flags.set("waveWidth",waveWidth); + flags.set("waveVol",waveVol); + flags.set("pcmVol",pcmVol); + }); + } + break; + } default: { bool sysPal=flags.getInt("clockSel",0);