Add SAXotone system
This commit is contained in:
parent
030488ed16
commit
79902f472f
8 changed files with 705 additions and 18 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
513
src/engine/platform/saxotone.cpp
Normal file
513
src/engine/platform/saxotone.cpp
Normal 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() {
|
||||
}
|
||||
92
src/engine/platform/saxotone.h
Normal file
92
src/engine/platform/saxotone.h
Normal 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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue