Add SAXotone system

This commit is contained in:
Natt Akuma 2026-01-22 18:15:06 +07:00
parent 030488ed16
commit 79902f472f
8 changed files with 705 additions and 18 deletions

View file

@ -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

View file

@ -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;

View file

@ -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 <math.h>
#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<len; h++) {
// tick (almost) every even lines
if (outputClock==0 || outputClock==3 || (outputClock>=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; i<MIN(parent->song.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; j<length; j++) {
sampleMem[startPos+j]=s->data8[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; i<MIN(parent->song.waveLen,256); i++) {
ws.changeWave1(i,true);
for (size_t j=0; j<waveWidth; j++) {
buf[j]=(ws.output[j]-128)*waveVol/256;
}
// overlap dedup, helps PWM waves
bool overlap=false;
size_t startPos=0;
int urPosR=0;
while (true) {
bool moving=true;
while (moving) {
moving=false;
// sample needs to start in a used region and end before the next used region
while (urPosR<urPosW && (startPos>=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)<waveWidth) {
startPos=(startPos+0xff)&~0xff;
moving=true;
}
}
if (urPosR>=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<urPosW-1) {
unsigned int newStartPos=usedRegions[urPosR];
if (usedRegions[urPosR+1]-newStartPos>=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<DivRegWrite>& 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() {
}

View file

@ -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 char> {
unsigned short sPosition;
unsigned char wave;
signed char out;
Channel():
SharedChannel<unsigned char>(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<DivRegWrite>& 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

View file

@ -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<<DIV_SAMPLE_DEPTH_8BIT, 0, 256,
@ -2679,16 +2679,16 @@ void DivEngine::registerSystems() {
}
);
sysDefs[DIV_SYSTEM_SID2]=new DivSysDef(
sysDefs[DIV_SYSTEM_SID2]=new DivSysDef(
_("SID2"), NULL, 0xf0, 0, 3, 3, 3,
false, true, 0, false, 0, 0, 0,
_("a fantasy sound chip created by LTVA. it is similar to the SID chip, but with many of its problems fixed."),
DivChanDefFunc(simpleChanDef<DIV_CH_NOISE,DIV_INS_SID2>),
{},
{},
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<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT), 256, 256,
_("a fantasy sound chip created by LTVA. it is a big rework of SID chip with probably too many features added on top."),
@ -2708,7 +2708,7 @@ void DivEngine::registerSystems() {
DIV_INS_SID3
);
}),
{},
{},
SID3PostEffectHandlerMap
);
@ -2736,6 +2736,21 @@ void DivEngine::registerSystems() {
c64PostEffectHandlerMap
);
sysDefs[DIV_SYSTEM_SAXOTONE]=new DivSysDef(
_("SAXotone"), NULL, 0xfe, 0, 5, 5, 5,
false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
_("Atari 2600 beeper system with wavetable channels and a mixed sample channel."),
DivChanDefFunc({
DivChanDef(_("Channel 1"), "CH1" , DIV_CH_WAVE, DIV_INS_SCC),
DivChanDef(_("Channel 2"), "CH2" , DIV_CH_WAVE, DIV_INS_SCC),
DivChanDef(_("Channel 3"), "CH3" , DIV_CH_WAVE, DIV_INS_SCC),
DivChanDef(_("Channel 4"), "CH4" , DIV_CH_WAVE, DIV_INS_SCC),
DivChanDef(_("PCM") , _("PCM"), DIV_CH_PCM , DIV_INS_AMIGA)
}),
{},
waveOnlyEffectHandlerMap
);
sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef(
_("Dummy System"), NULL, 0xfd, 0, 8, 1, 128,
false, true, 0, false, 0, 0, 0,

View file

@ -168,7 +168,7 @@ enum DivSystem {
DIV_SYSTEM_MSX2, // ** COMPOUND SYSTEM - DO NOT USE! **
DIV_SYSTEM_YM2610_CRAP,
DIV_SYSTEM_YM2610_CRAP_EXT,
DIV_SYSTEM_AY8910,
DIV_SYSTEM_AMIGA,
DIV_SYSTEM_YM2151,
@ -265,6 +265,7 @@ enum DivSystem {
DIV_SYSTEM_UPD1771C,
DIV_SYSTEM_SID3,
DIV_SYSTEM_C64_PCM,
DIV_SYSTEM_SAXOTONE,
DIV_SYSTEM_MAX
};

View file

@ -264,7 +264,7 @@ const char* chanNames[CHANNEL_TYPE_MAX+1]={
_N("Square"),
_N("Triangle"), // NES
_N("Saw"), // VRC6
_N("Ext. Operator"),
_N("Ext. Operator"),
_N("Drums"),
_N("Slope"), // PowerNoise
_N("Wave"), // not wavetable (VERA, 5E01)
@ -1363,6 +1363,7 @@ const int availableSystems[]={
DIV_SYSTEM_UPD1771C,
DIV_SYSTEM_SID3,
DIV_SYSTEM_MULTIPCM,
DIV_SYSTEM_SAXOTONE,
0 // don't remove this last one!
};

View file

@ -32,7 +32,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
switch (type) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_DUALPCM:
case DIV_SYSTEM_YM2612_DUALPCM_EXT:
case DIV_SYSTEM_YM2612_CSM: {
@ -109,7 +109,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
if (interruptSimCycles>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<<i));
@ -2274,7 +2274,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
altered=true;
} rightClickable
}
ImGui::Text(_("Initial part volume (channel 1-4):"));
for (int i=0; i<4; i++) {
snprintf(temp,63,"%d'##GRPV%d",2<<i,i);
@ -2841,6 +2841,66 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
}
break;
}
case DIV_SYSTEM_SAXOTONE: {
bool sysPal=flags.getInt("clockSel",0);
int waveWidth=flags.getInt("waveWidth",1);
int waveVol=flags.getInt("waveVol",32);
int pcmVol=flags.getInt("pcmVol",128);
if (ImGui::Checkbox(_("PAL"),&sysPal)) {
altered=true;
}
ImGui::Text(_("Wavetable volume:"));
if (CWSliderInt("##waveVol",&waveVol,0,128)) {
if (waveVol<0) waveVol=0;
if (waveVol>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);