This commit is contained in:
tildearrow 2022-09-24 02:54:28 -05:00
commit b7e618e91d
19 changed files with 2616 additions and 70 deletions

View file

@ -437,6 +437,8 @@ src/engine/platform/sound/rf5c68.cpp
src/engine/platform/sound/oki/okim6258.cpp src/engine/platform/sound/oki/okim6258.cpp
src/engine/platform/sound/snes/SPC_DSP.cpp
src/engine/platform/oplAInterface.cpp src/engine/platform/oplAInterface.cpp
src/engine/platform/ym2608Interface.cpp src/engine/platform/ym2608Interface.cpp
src/engine/platform/ym2610Interface.cpp src/engine/platform/ym2610Interface.cpp
@ -509,6 +511,7 @@ src/engine/platform/scc.cpp
src/engine/platform/ymz280b.cpp src/engine/platform/ymz280b.cpp
src/engine/platform/namcowsg.cpp src/engine/platform/namcowsg.cpp
src/engine/platform/rf5c68.cpp src/engine/platform/rf5c68.cpp
src/engine/platform/snes.cpp
src/engine/platform/pcmdac.cpp src/engine/platform/pcmdac.cpp
src/engine/platform/dummy.cpp src/engine/platform/dummy.cpp
) )

View file

@ -36,5 +36,6 @@ this is a list of systems that Furnace supports, including each system's effects
- [Konami VRC6](vrc6.md) - [Konami VRC6](vrc6.md)
- [Famicom Disk System](fds.md) - [Famicom Disk System](fds.md)
- [Nintendo MMC5](mmc5.md) - [Nintendo MMC5](mmc5.md)
- [SNES](snes.md)
Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but... Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but...

View file

@ -0,0 +1,18 @@
# Super NES
The successor to NES to compete with Genesis. Now packing with superior graphics and sample-based audio. Also known as Super Famicom.
Its audio subsystem, developed by Sony, features the DSP chip, SPC700 microcontroller and 64KB of dedicated SRAM used by both. This whole system itself is pretty much a separate computer that the main CPU needs to upload its program and samples to.
The DSP chip can
Furnace communicates with the DSP directly and provide a full 64KB memory. This memory might be reduced excessively on ROM export to make up for playback engine and pattern data.
# effects
Note: this chip has a signed left/right level. Which can be used for inverted (surround) stereo. A signed 8-bit value means 80 - FF = -128 - -1. Other values work normally. A value of -128 is not recommended as it could cause overflows.
- `10xx`: Set echo feedback level. This effect will apply to all channels.
- `11xx`: Set echo left level (signed 8-bit). This effect will apply to all channels.
- `12xx`: Set echo right level (signed 8-bit). This effect will apply to all channels.
- `13xx`: Set the length of the echo delay buffer. This will also affect the size of the sample RAM!

View file

@ -65,6 +65,7 @@
#include "platform/scc.h" #include "platform/scc.h"
#include "platform/ymz280b.h" #include "platform/ymz280b.h"
#include "platform/rf5c68.h" #include "platform/rf5c68.h"
#include "platform/snes.h"
#include "platform/pcmdac.h" #include "platform/pcmdac.h"
#include "platform/dummy.h" #include "platform/dummy.h"
#include "../ta-log.h" #include "../ta-log.h"
@ -398,6 +399,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
dispatch=new DivPlatformNamcoWSG; dispatch=new DivPlatformNamcoWSG;
((DivPlatformNamcoWSG*)dispatch)->setDeviceType(30); ((DivPlatformNamcoWSG*)dispatch)->setDeviceType(30);
break; break;
case DIV_SYSTEM_SNES:
dispatch=new DivPlatformSNES;
break;
case DIV_SYSTEM_PCM_DAC: case DIV_SYSTEM_PCM_DAC:
dispatch=new DivPlatformPCMDAC; dispatch=new DivPlatformPCMDAC;
break; break;

View file

@ -559,9 +559,10 @@ void DivInstrument::putInsData(SafeWriter* w) {
w->writeC(es5506.envelope.k2Slow); w->writeC(es5506.envelope.k2Slow);
// SNES // SNES
// @tildearrow please update this
w->writeC(snes.useEnv); w->writeC(snes.useEnv);
w->writeC(snes.gainMode); w->writeC(0);
w->writeC(snes.gain); w->writeC(0);
w->writeC(snes.a); w->writeC(snes.a);
w->writeC(snes.d); w->writeC(snes.d);
w->writeC(snes.s); w->writeC(snes.s);
@ -1258,8 +1259,8 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
// SNES // SNES
if (version>=109) { if (version>=109) {
snes.useEnv=reader.readC(); snes.useEnv=reader.readC();
snes.gainMode=(DivInstrumentSNES::GainMode)reader.readC(); reader.readC();
snes.gain=reader.readC(); reader.readC();
snes.a=reader.readC(); snes.a=reader.readC();
snes.d=reader.readC(); snes.d=reader.readC();
snes.s=reader.readC(); snes.s=reader.readC();

View file

@ -520,21 +520,21 @@ struct DivInstrumentES5506 {
}; };
struct DivInstrumentSNES { struct DivInstrumentSNES {
enum GainMode: unsigned char { // Purposeful Conflict enum GainMode: unsigned char {
GAIN_MODE_DIRECT=0, // Purposeful Conflict GAIN_MODE_DIRECT=0,
GAIN_MODE_DEC_LINEAR=4, // Purposeful Conflict GAIN_MODE_DEC_LINEAR=4,
GAIN_MODE_DEC_LOG=5, // Purposeful Conflict GAIN_MODE_DEC_LOG=5,
GAIN_MODE_INC_LINEAR=6, // Purposeful Conflict GAIN_MODE_INC_LINEAR=6,
GAIN_MODE_INC_INVLOG=7 // Purposeful Conflict GAIN_MODE_INC_INVLOG=7
}; };
bool useEnv; bool useEnv;
GainMode gainMode; // Purposeful Conflict GainMode gainMode;
unsigned char gain; // Purposeful Conflict unsigned char gain;
unsigned char a, d, s, r; unsigned char a, d, s, r;
DivInstrumentSNES(): DivInstrumentSNES():
useEnv(true), useEnv(true),
gainMode(GAIN_MODE_DIRECT), // Purposeful Conflict gainMode(GAIN_MODE_DIRECT),
gain(127), // Purposeful Conflict gain(127),
a(15), a(15),
d(7), d(7),
s(7), s(7),

View file

@ -0,0 +1,518 @@
/**
* 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 "snes.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <math.h>
#define CHIP_FREQBASE 131072
#define rWrite(a,v) {dsp.write(a,v); regPool[(a)&0x7f]=v; }
#define chWrite(c,a,v) {rWrite((a)+(c)*16,v)}
#define sampleTableAddr(c) (sampleTableBase+(c)*4)
#define waveTableAddr(c) (sampleTableBase+8*4+(c)*9*16)
const char* regCheatSheetSNESDSP[]={
"VxVOLL", "x0",
"VxVOLR", "x1",
"VxPITCHL", "x2",
"VxPITCHH", "x3",
"VxSRCN", "x4",
"VxADSR1", "x5",
"VxADSR2", "x6",
"VxGAIN", "x7",
"VxENVX", "x8",
"VxOUTX", "x9",
"FIRx", "xF",
"MVOLL", "0C",
"MVOLR", "1C",
"EVOLL", "2C",
"EVOLR", "3C",
"KON", "4C",
"KOFF", "5C",
"FLG", "6C",
"ENDX", "7C",
"EFB", "0D",
"PMON", "2D",
"NON", "3D",
"EON", "4D",
"DIR", "5D",
"ESA", "6D",
"EDL", "7D",
NULL
};
const char** DivPlatformSNES::getRegisterSheet() {
return regCheatSheetSNESDSP;
}
void DivPlatformSNES::acquire(short* bufL, short* bufR, size_t start, size_t len) {
short out[2];
short chOut[16];
for (size_t h=start; h<start+len; h++) {
dsp.set_output(out,1);
dsp.run(32);
dsp.get_voice_outputs(chOut);
bufL[h]=out[0];
bufR[h]=out[1];
for (int i=0; i<8; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=chOut[i*2]+chOut[i*2+1];
}
}
}
void DivPlatformSNES::tick(bool sysTick) {
// KON/KOFF can't be written several times per one sample
// so they have to be accumulated
unsigned char kon=0;
unsigned char koff=0;
for (int i=0; i<8; i++) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
bool hadGain=chan[i].std.vol.had || chan[i].std.ex1.had || chan[i].std.ex2.had;
chan[i].std.next();
if (ins->type==DIV_INS_AMIGA && chan[i].std.vol.had) {
chWrite(i,7,MIN(127,chan[i].std.vol.val*2));
} else if (!chan[i].useEnv && hadGain) {
if (chan[i].std.ex1.val==0) {
// direct gain
chWrite(i,7,chan[i].std.vol.val);
} else {
// inc/dec
chWrite(i,7,chan[i].std.ex2.val|((chan[i].std.ex1.val-1)<<5)|0x80);
}
}
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
} else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
}
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
chan[i].freqChanged=true;
}
}
if (chan[i].useWave && 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,-32768,32767);
} else {
chan[i].pitch2=chan[i].std.pitch.val;
}
chan[i].freqChanged=true;
}
if (chan[i].std.panL.had) {
int val=chan[i].std.panL.val&0x7f;
chan[i].panL=(val<<1)|(val>>6);
}
if (chan[i].std.panR.had) {
int val=chan[i].std.panR.val&0x7f;
chan[i].panR=(val<<1)|(val>>6);
}
if (chan[i].std.panL.had || chan[i].std.panR.had) {
writeOutVol(i);
}
if (chan[i].setPos) {
// force keyon
chan[i].keyOn=true;
chan[i].setPos=false;
} else {
chan[i].audPos=0;
}
if (chan[i].useWave && chan[i].active) {
if (chan[i].ws.tick()) {
updateWave(i);
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
DivSample* s=parent->getSample(chan[i].sample);
double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
chan[i].freq=(unsigned int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE));
if (chan[i].freq>16383) chan[i].freq=16383;
if (chan[i].keyOn) {
unsigned int start, end, loop;
size_t tabAddr=sampleTableAddr(i);
if (chan[i].useEnv) {
chWrite(i,5,ins->snes.a|(ins->snes.d<<4)|0x80);
chWrite(i,6,ins->snes.r|(ins->snes.s<<5));
} else {
chWrite(i,5,0);
}
if (chan[i].useWave) {
start=waveTableAddr(i);
loop=start;
} else {
start=s->offSNES;
end=MIN(start+MAX(s->lengthBRR,1),getSampleMemCapacity());
loop=MAX(start,end-1);
if (chan[i].audPos>0) {
start=start+MIN(chan[i].audPos,s->lengthBRR-1)/16*9;
}
if (s->loopStart>=0) {
loop=start+s->loopStart/16*9;
}
}
sampleMem[tabAddr+0]=start&0xff;
sampleMem[tabAddr+1]=start>>8;
sampleMem[tabAddr+2]=loop&0xff;
sampleMem[tabAddr+3]=loop>>8;
if (!hadGain) {
chWrite(i,7,0x7f);
}
kon|=(1<<i);
chan[i].keyOn=false;
}
if (chan[i].keyOff) {
koff|=(1<<i);
chan[i].keyOff=false;
}
if (chan[i].freqChanged) {
chWrite(i,2,chan[i].freq&0xff);
chWrite(i,3,chan[i].freq>>8);
chan[i].freqChanged=false;
}
}
}
if (kon!=0) {
rWrite(0x4c,kon);
}
// always write KOFF as it's constantly polled
rWrite(0x5c,koff);
}
int DivPlatformSNES::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
if (ins->amiga.useWave) {
chan[c.chan].useWave=true;
chan[c.chan].wtLen=ins->amiga.waveLen+1;
if (chan[c.chan].insChanged) {
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
chan[c.chan].ws.setWidth(chan[c.chan].wtLen);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
}
chan[c.chan].ws.init(ins,chan[c.chan].wtLen,15,chan[c.chan].insChanged);
} else {
chan[c.chan].sample=ins->amiga.getSample(c.value);
chan[c.chan].useWave=false;
}
if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1;
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=round(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);
if (ins->type==DIV_INS_SNES) {
// initialize to max gain in case of direct gain mode macro without gain level macro
chan[c.chan].std.vol.val=0x7f;
chan[c.chan].useEnv=ins->snes.useEnv;
}
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].sample=-1;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
case DIV_CMD_INSTRUMENT:
if (chan[c.chan].ins!=c.value || c.value2==1) {
chan[c.chan].ins=c.value;
chan[c.chan].insChanged=true;
}
break;
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
writeOutVol(c.chan);
}
break;
// case DIV_CMD_GLOBAL_VOLUME:
// gblVolL=MIN(c.value,127);
// gblVolR=MIN(c.value,127);
// rWrite(0x0c,gblVolL);
// rWrite(0x1c,gblVolR);
// break;
case DIV_CMD_GET_VOLUME:
return chan[c.chan].vol;
break;
case DIV_CMD_PANNING:
chan[c.chan].panL=c.value;
chan[c.chan].panR=c.value2;
writeOutVol(c.chan);
break;
case DIV_CMD_PITCH:
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=round(NOTE_FREQUENCY(c.value2));
bool return2=false;
if (destFreq>chan[c.chan].baseFreq) {
chan[c.chan].baseFreq+=c.value;
if (chan[c.chan].baseFreq>=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
} else {
chan[c.chan].baseFreq-=c.value;
if (chan[c.chan].baseFreq<=destFreq) {
chan[c.chan].baseFreq=destFreq;
return2=true;
}
}
chan[c.chan].freqChanged=true;
if (return2) {
chan[c.chan].inPorta=false;
return 2;
}
break;
}
case DIV_CMD_LEGATO: {
chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
break;
}
case DIV_CMD_PRE_PORTA:
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
}
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_SAMPLE_POS:
chan[c.chan].audPos=c.value;
chan[c.chan].setPos=true;
break;
// TODO SNES-specific commands
case DIV_CMD_GET_VOLMAX:
return 127;
break;
default:
break;
}
return 1;
}
void DivPlatformSNES::updateWave(int ch) {
// Due to the overflow bug in hardware's resampler, the written amplitude here is half of maximum
size_t pos=waveTableAddr(ch);
for (int i=0; i<chan[ch].wtLen/16; i++) {
sampleMem[pos++]=0xb0;
for (int j=0; j<8; j++) {
int nibble1=(chan[ch].ws.output[i*16+j*2]-8)&15;
int nibble2=(chan[ch].ws.output[i*16+j*2+1]-8)&15;
sampleMem[pos++]=(nibble1<<4)|nibble2;
}
}
sampleMem[pos-9]=0xb3; // mark loop
}
void DivPlatformSNES::writeOutVol(int ch) {
// TODO negative (inverted) panning support
int outL=0;
int outR=0;
if (!isMuted[ch]) {
outL=chan[ch].vol*chan[ch].panL/255;
outR=chan[ch].vol*chan[ch].panR/255;
}
chWrite(ch,0,outL);
chWrite(ch,1,outR);
}
void DivPlatformSNES::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
writeOutVol(ch);
}
void DivPlatformSNES::forceIns() {
for (int i=0; i<8; i++) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
chan[i].sample=-1;
updateWave(i);
}
}
void* DivPlatformSNES::getChanState(int ch) {
return &chan[ch];
}
DivMacroInt* DivPlatformSNES::getChanMacroInt(int ch) {
return &chan[ch].std;
}
DivDispatchOscBuffer* DivPlatformSNES::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformSNES::getRegisterPool() {
// get states from emulator
for (int i=0; i<0x80; i+=0x10) {
regPool[i+8]=dsp.read(i+8);
regPool[i+9]=dsp.read(i+9);
}
regPool[0x7c]=dsp.read(0x7c); // ENDX
return regPool;
}
int DivPlatformSNES::getRegisterPoolSize() {
return 128;
}
void DivPlatformSNES::reset() {
dsp.init(sampleMem);
dsp.set_output(NULL,0);
memset(regPool,0,128);
// TODO more initial values
sampleTableBase=0x100; // hack: this can't be 0 or channel 1 won't play??
rWrite(0x5d,sampleTableBase>>8);
rWrite(0x0c,127); // global volume left
rWrite(0x1c,127); // global volume right
rWrite(0x6c,0); // get DSP out of reset
for (int i=0; i<8; i++) {
chan[i]=Channel();
chan[i].std.setEngine(parent);
chan[i].ws.setEngine(parent);
chan[i].ws.init(NULL,32,255);
writeOutVol(i);
chWrite(i,4,i); // source number
}
}
bool DivPlatformSNES::isStereo() {
return true;
}
void DivPlatformSNES::notifyInsChange(int ins) {
for (int i=0; i<8; i++) {
if (chan[i].ins==ins) {
chan[i].insChanged=true;
}
}
}
void DivPlatformSNES::notifyWaveChange(int wave) {
for (int i=0; i<8; i++) {
if (chan[i].useWave && chan[i].wave==wave) {
chan[i].ws.changeWave1(wave);
if (chan[i].active) {
updateWave(i);
}
}
}
}
void DivPlatformSNES::notifyInsDeletion(void* ins) {
for (int i=0; i<8; i++) {
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
}
}
void DivPlatformSNES::poke(unsigned int addr, unsigned short val) {
rWrite(addr,val);
}
void DivPlatformSNES::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
const void* DivPlatformSNES::getSampleMem(int index) {
return index == 0 ? sampleMem : NULL;
}
size_t DivPlatformSNES::getSampleMemCapacity(int index) {
// TODO change it based on current echo buffer size
return index == 0 ? 65536 : 0;
}
size_t DivPlatformSNES::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
void DivPlatformSNES::renderSamples() {
memset(sampleMem,0,getSampleMemCapacity());
// skip past sample table and wavetable buffer
size_t memPos=sampleTableBase+8*4+8*9*16;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
int length=s->lengthBRR;
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)/9*9,length);
if (actualLength>0) {
s->offSNES=memPos;
memcpy(&sampleMem[memPos],s->data8,actualLength);
memPos+=actualLength;
}
if (actualLength<length) {
// terminate the sample
sampleMem[memPos-9]=1;
logW("out of BRR memory for sample %d!",i);
break;
}
}
sampleMemLen=memPos;
}
int DivPlatformSNES::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<8; i++) {
oscBuf[i]=new DivDispatchOscBuffer;
isMuted[i]=false;
}
sampleMemLen=0;
chipClock=1024000;
rate=chipClock/32;
reset();
return 8;
}
void DivPlatformSNES::quit() {
for (int i=0; i<8; i++) {
delete oscBuf[i];
}
}

112
src/engine/platform/snes.h Normal file
View file

@ -0,0 +1,112 @@
/**
* 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 _SNES_H
#define _SNES_H
#include "../dispatch.h"
#include "../macroInt.h"
#include "../waveSynth.h"
#include <queue>
#include "sound/snes/SPC_DSP.h"
class DivPlatformSNES: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2;
unsigned int audPos;
int sample, wave, ins;
int note;
int panL, panR;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos;
signed char vol;
int wtLen;
bool useEnv;
DivMacroInt std;
DivWaveSynth ws;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
Channel():
freq(0),
baseFreq(0),
pitch(0),
pitch2(0),
audPos(0),
sample(-1),
wave(-1),
ins(-1),
note(0),
panL(255),
panR(255),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
useWave(false),
setPos(false),
vol(127),
wtLen(16),
useEnv(false) {}
};
Channel chan[8];
DivDispatchOscBuffer* oscBuf[8];
bool isMuted[8];
signed char gblVolL, gblVolR;
size_t sampleTableBase;
signed char sampleMem[65536];
size_t sampleMemLen;
unsigned char regPool[0x80];
SPC_DSP dsp;
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);
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 isStereo();
void notifyInsChange(int ins);
void notifyWaveChange(int wave);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const char** getRegisterSheet();
const void* getSampleMem(int index = 0);
size_t getSampleMemCapacity(int index = 0);
size_t getSampleMemUsage(int index = 0);
void renderSamples();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
private:
void updateWave(int ch);
void writeOutVol(int ch);
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,322 @@
// Highly accurate SNES SPC-700 DSP emulator
// snes_spc 0.9.0
#ifndef SPC_DSP_H
#define SPC_DSP_H
#include "blargg_common.h"
extern "C" { typedef void (*dsp_copy_func_t)( unsigned char** io, void* state, size_t ); }
class SPC_DSP {
public:
typedef BOOST::uint8_t uint8_t;
// Setup
// Initializes DSP and has it use the 64K RAM provided
void init( void* ram_64k );
// Sets destination for output samples. If out is NULL or out_size is 0,
// doesn't generate any.
typedef short sample_t;
void set_output( sample_t* out, int out_size );
// Number of samples written to output since it was last set, always
// a multiple of 2. Undefined if more samples were generated than
// output buffer could hold.
int sample_count() const;
// Emulation
// Resets DSP to power-on state
void reset();
// Emulates pressing reset switch on SNES
void soft_reset();
// Reads/writes DSP registers. For accuracy, you must first call run()
// to catch the DSP up to present.
int read ( int addr ) const;
void write( int addr, int data );
// Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks
// a pair of samples is be generated.
void run( int clock_count );
// Sound control
// Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events).
// Reduces emulation accuracy.
enum { voice_count = 8 };
void mute_voices( int mask );
// State
// Resets DSP and uses supplied values to initialize registers
enum { register_count = 128 };
void load( uint8_t const regs [register_count] );
// Saves/loads exact emulator state
enum { state_size = 640 }; // maximum space needed when saving
typedef dsp_copy_func_t copy_func_t;
void copy_state( unsigned char** io, copy_func_t );
// Returns non-zero if new key-on events occurred since last call
bool check_kon();
// Furnace addition, gets all current voice outputs to an array of samples
void get_voice_outputs( sample_t* outs );
// DSP register addresses
// Global registers
enum {
r_mvoll = 0x0C, r_mvolr = 0x1C,
r_evoll = 0x2C, r_evolr = 0x3C,
r_kon = 0x4C, r_koff = 0x5C,
r_flg = 0x6C, r_endx = 0x7C,
r_efb = 0x0D, r_pmon = 0x2D,
r_non = 0x3D, r_eon = 0x4D,
r_dir = 0x5D, r_esa = 0x6D,
r_edl = 0x7D,
r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F
};
// Voice registers
enum {
v_voll = 0x00, v_volr = 0x01,
v_pitchl = 0x02, v_pitchh = 0x03,
v_srcn = 0x04, v_adsr0 = 0x05,
v_adsr1 = 0x06, v_gain = 0x07,
v_envx = 0x08, v_outx = 0x09
};
public:
enum { extra_size = 16 };
sample_t* extra() { return m.extra; }
sample_t const* out_pos() const { return m.out; }
void disable_surround( bool ) { } // not supported
public:
BLARGG_DISABLE_NOTHROW
typedef BOOST::int8_t int8_t;
typedef BOOST::int16_t int16_t;
enum { echo_hist_size = 8 };
enum env_mode_t { env_release, env_attack, env_decay, env_sustain };
enum { brr_buf_size = 12 };
struct voice_t
{
int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling)
int buf_pos; // place in buffer where next samples will be decoded
int interp_pos; // relative fractional position in sample (0x1000 = 1.0)
int brr_addr; // address of current BRR block
int brr_offset; // current decoding offset in BRR block
uint8_t* regs; // pointer to voice's DSP registers
int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc.
int kon_delay; // KON delay/current setup phase
env_mode_t env_mode;
int env; // current envelope level
int hidden_env; // used by GAIN mode 7, very obscure quirk
uint8_t t_envx_out;
sample_t out[2]; // Furnace addition, for per-channel oscilloscope
};
private:
enum { brr_block_size = 9 };
struct state_t
{
uint8_t regs [register_count];
// Echo history keeps most recent 8 samples (twice the size to simplify wrap handling)
int echo_hist [echo_hist_size * 2] [2];
int (*echo_hist_pos) [2]; // &echo_hist [0 to 7]
int every_other_sample; // toggles every sample
int kon; // KON value when last checked
int noise;
int counter;
int echo_offset; // offset from ESA in echo buffer
int echo_length; // number of bytes that echo_offset will stop at
int phase; // next clock cycle to run (0-31)
bool kon_check; // set when a new KON occurs
// Hidden registers also written to when main register is written to
int new_kon;
uint8_t endx_buf;
uint8_t envx_buf;
uint8_t outx_buf;
// Temporary state between clocks
// read once per sample
int t_pmon;
int t_non;
int t_eon;
int t_dir;
int t_koff;
// read a few clocks ahead then used
int t_brr_next_addr;
int t_adsr0;
int t_brr_header;
int t_brr_byte;
int t_srcn;
int t_esa;
int t_echo_enabled;
// internal state that is recalculated every sample
int t_dir_addr;
int t_pitch;
int t_output;
int t_looped;
int t_echo_ptr;
// left/right sums
int t_main_out [2];
int t_echo_out [2];
int t_echo_in [2];
voice_t voices [voice_count];
// non-emulation state
uint8_t* ram; // 64K shared RAM between DSP and SMP
int mute_mask;
sample_t* out;
sample_t* out_end;
sample_t* out_begin;
sample_t extra [extra_size];
};
state_t m;
void init_counter();
void run_counters();
unsigned read_counter( int rate );
int interpolate( voice_t const* v );
void run_envelope( voice_t* const v );
void decode_brr( voice_t* v );
void misc_27();
void misc_28();
void misc_29();
void misc_30();
void voice_output( voice_t* const v, int ch );
void voice_V1( voice_t* const );
void voice_V2( voice_t* const );
void voice_V3( voice_t* const );
void voice_V3a( voice_t* const );
void voice_V3b( voice_t* const );
void voice_V3c( voice_t* const );
void voice_V4( voice_t* const );
void voice_V5( voice_t* const );
void voice_V6( voice_t* const );
void voice_V7( voice_t* const );
void voice_V8( voice_t* const );
void voice_V9( voice_t* const );
void voice_V7_V4_V1( voice_t* const );
void voice_V8_V5_V2( voice_t* const );
void voice_V9_V6_V3( voice_t* const );
void echo_read( int ch );
int echo_output( int ch );
void echo_write( int ch );
void echo_22();
void echo_23();
void echo_24();
void echo_25();
void echo_26();
void echo_27();
void echo_28();
void echo_29();
void echo_30();
void soft_reset_common();
public:
bool mute() { return m.regs[r_flg] & 0x40; }
};
#include <assert.h>
inline int SPC_DSP::sample_count() const { return m.out - m.out_begin; }
inline int SPC_DSP::read( int addr ) const
{
assert( (unsigned) addr < register_count );
return m.regs [addr];
}
inline void SPC_DSP::write( int addr, int data )
{
assert( (unsigned) addr < register_count );
m.regs [addr] = (uint8_t) data;
switch ( addr & 0x0F )
{
case v_envx:
m.envx_buf = (uint8_t) data;
break;
case v_outx:
m.outx_buf = (uint8_t) data;
break;
case 0x0C:
if ( addr == r_kon )
m.new_kon = (uint8_t) data;
if ( addr == r_endx ) // always cleared, regardless of data written
{
m.endx_buf = 0;
m.regs [r_endx] = 0;
}
break;
}
}
inline void SPC_DSP::mute_voices( int mask ) { m.mute_mask = mask; }
inline bool SPC_DSP::check_kon()
{
bool old = m.kon_check;
m.kon_check = 0;
return old;
}
inline void SPC_DSP::get_voice_outputs( sample_t* outs )
{
int i;
for ( i = 0; i < voice_count; i++ )
{
voice_t* v = &m.voices [i];
outs [i * 2] = v->out [0];
outs [i * 2 + 1] = v->out [1];
}
}
#if !SPC_NO_COPY_STATE_FUNCS
class SPC_State_Copier {
SPC_DSP::copy_func_t func;
unsigned char** buf;
public:
SPC_State_Copier( unsigned char** p, SPC_DSP::copy_func_t f ) { func = f; buf = p; }
void copy( void* state, size_t size );
int copy_int( int state, int size );
void skip( int count );
void extra();
};
#define SPC_COPY( type, state )\
{\
state = (BOOST::type) copier.copy_int( state, sizeof (BOOST::type) );\
assert( (BOOST::type) state == state );\
}
#endif
#endif

View file

@ -0,0 +1,186 @@
// Sets up common environment for Shay Green's libraries.
// To change configuration options, modify blargg_config.h, not this file.
// snes_spc 0.9.0
#ifndef BLARGG_COMMON_H
#define BLARGG_COMMON_H
#include <stddef.h>
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#undef BLARGG_COMMON_H
// allow blargg_config.h to #include blargg_common.h
#include "blargg_config.h"
#ifndef BLARGG_COMMON_H
#define BLARGG_COMMON_H
// BLARGG_RESTRICT: equivalent to restrict, where supported
#if defined (__GNUC__) || _MSC_VER >= 1100
#define BLARGG_RESTRICT __restrict
#else
#define BLARGG_RESTRICT
#endif
// STATIC_CAST(T,expr): Used in place of static_cast<T> (expr)
#ifndef STATIC_CAST
#define STATIC_CAST(T,expr) ((T) (expr))
#endif
// blargg_err_t (0 on success, otherwise error string)
#ifndef blargg_err_t
typedef const char* blargg_err_t;
#endif
// blargg_vector - very lightweight vector of POD types (no constructor/destructor)
template<class T>
class blargg_vector {
T* begin_;
size_t size_;
public:
blargg_vector() : begin_( 0 ), size_( 0 ) { }
~blargg_vector() { free( begin_ ); }
size_t size() const { return size_; }
T* begin() const { return begin_; }
T* end() const { return begin_ + size_; }
blargg_err_t resize( size_t n )
{
// TODO: blargg_common.cpp to hold this as an outline function, ugh
void* p = realloc( begin_, n * sizeof (T) );
if ( p )
begin_ = (T*) p;
else if ( n > size_ ) // realloc failure only a problem if expanding
return "Out of memory";
size_ = n;
return 0;
}
void clear() { void* p = begin_; begin_ = 0; size_ = 0; free( p ); }
T& operator [] ( size_t n ) const
{
assert( n <= size_ ); // <= to allow past-the-end value
return begin_ [n];
}
};
#ifndef BLARGG_DISABLE_NOTHROW
// throw spec mandatory in ISO C++ if operator new can return NULL
#if __cplusplus >= 199711 || defined (__GNUC__)
#define BLARGG_THROWS( spec ) throw spec
#else
#define BLARGG_THROWS( spec )
#endif
#define BLARGG_DISABLE_NOTHROW \
void* operator new ( size_t s ) BLARGG_THROWS(()) { return malloc( s ); }\
void operator delete ( void* p ) { free( p ); }
#define BLARGG_NEW new
#else
#include <new>
#define BLARGG_NEW new (std::nothrow)
#endif
// BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant)
#define BLARGG_4CHAR( a, b, c, d ) \
((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF))
// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0.
#ifndef BOOST_STATIC_ASSERT
#ifdef _MSC_VER
// MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified
#define BOOST_STATIC_ASSERT( expr ) \
void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] )
#else
// Some other compilers fail when declaring same function multiple times in class,
// so differentiate them by line
#define BOOST_STATIC_ASSERT( expr ) \
void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] )
#endif
#endif
// BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1,
// compiler is assumed to support bool. If undefined, availability is determined.
#ifndef BLARGG_COMPILER_HAS_BOOL
#if defined (__MWERKS__)
#if !__option(bool)
#define BLARGG_COMPILER_HAS_BOOL 0
#endif
#elif defined (_MSC_VER)
#if _MSC_VER < 1100
#define BLARGG_COMPILER_HAS_BOOL 0
#endif
#elif defined (__GNUC__)
// supports bool
#elif __cplusplus < 199711
#define BLARGG_COMPILER_HAS_BOOL 0
#endif
#endif
#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL
// If you get errors here, modify your blargg_config.h file
typedef int bool;
const bool true = 1;
const bool false = 0;
#endif
// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough
#if INT_MAX < 0x7FFFFFFF || LONG_MAX == 0x7FFFFFFF
typedef long blargg_long;
#else
typedef int blargg_long;
#endif
#if UINT_MAX < 0xFFFFFFFF || ULONG_MAX == 0xFFFFFFFF
typedef unsigned long blargg_ulong;
#else
typedef unsigned blargg_ulong;
#endif
// BOOST::int8_t etc.
// HAVE_STDINT_H: If defined, use <stdint.h> for int8_t etc.
#if defined (HAVE_STDINT_H)
#include <stdint.h>
#define BOOST
// HAVE_INTTYPES_H: If defined, use <stdint.h> for int8_t etc.
#elif defined (HAVE_INTTYPES_H)
#include <inttypes.h>
#define BOOST
#else
struct BOOST
{
#if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F
typedef signed char int8_t;
typedef unsigned char uint8_t;
#else
// No suitable 8-bit type available
typedef struct see_blargg_common_h int8_t;
typedef struct see_blargg_common_h uint8_t;
#endif
#if USHRT_MAX == 0xFFFF
typedef short int16_t;
typedef unsigned short uint16_t;
#else
// No suitable 16-bit type available
typedef struct see_blargg_common_h int16_t;
typedef struct see_blargg_common_h uint16_t;
#endif
#if ULONG_MAX == 0xFFFFFFFF
typedef long int32_t;
typedef unsigned long uint32_t;
#elif UINT_MAX == 0xFFFFFFFF
typedef int int32_t;
typedef unsigned int uint32_t;
#else
// No suitable 32-bit type available
typedef struct see_blargg_common_h int32_t;
typedef struct see_blargg_common_h uint32_t;
#endif
};
#endif
#endif
#endif

View file

@ -0,0 +1,26 @@
// snes_spc 0.9.0 user configuration file. Don't replace when updating library.
// snes_spc 0.9.0
#ifndef BLARGG_CONFIG_H
#define BLARGG_CONFIG_H
// Uncomment to disable debugging checks
#ifndef NDEBUG
#define NDEBUG 1
#endif
// Uncomment to enable platform-specific (and possibly non-portable) optimizations
//#define BLARGG_NONPORTABLE 1
// Uncomment if automatic byte-order determination doesn't work
//#define BLARGG_BIG_ENDIAN 1
// Uncomment if you get errors in the bool section of blargg_common.h
//#define BLARGG_COMPILER_HAS_BOOL 1
// Use standard config.h if present
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#endif

View file

@ -0,0 +1,185 @@
// CPU Byte Order Utilities
// snes_spc 0.9.0
#ifndef BLARGG_ENDIAN
#define BLARGG_ENDIAN
#include "blargg_common.h"
// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16)
#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \
defined (__x86_64__) || defined (__ia64__) || defined (__i386__)
#define BLARGG_CPU_X86 1
#define BLARGG_CPU_CISC 1
#endif
#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc)
#define BLARGG_CPU_POWERPC 1
#define BLARGG_CPU_RISC 1
#endif
// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only
// one may be #defined to 1. Only needed if something actually depends on byte order.
#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN)
#ifdef __GLIBC__
// GCC handles this for us
#include <endian.h>
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define BLARGG_LITTLE_ENDIAN 1
#elif __BYTE_ORDER == __BIG_ENDIAN
#define BLARGG_BIG_ENDIAN 1
#endif
#else
#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \
(defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234)
#define BLARGG_LITTLE_ENDIAN 1
#endif
#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \
defined (__sparc__) || BLARGG_CPU_POWERPC || \
(defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321)
#define BLARGG_BIG_ENDIAN 1
#elif !defined (__mips__)
// No endian specified; assume little-endian, since it's most common
#define BLARGG_LITTLE_ENDIAN 1
#endif
#endif
#endif
#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN
#undef BLARGG_LITTLE_ENDIAN
#undef BLARGG_BIG_ENDIAN
#endif
inline void blargg_verify_byte_order()
{
#ifndef NDEBUG
#if BLARGG_BIG_ENDIAN
volatile int i = 1;
assert( *(volatile char*) &i == 0 );
#elif BLARGG_LITTLE_ENDIAN
volatile int i = 1;
assert( *(volatile char*) &i != 0 );
#endif
#endif
}
inline unsigned get_le16( void const* p )
{
return (unsigned) ((unsigned char const*) p) [1] << 8 |
(unsigned) ((unsigned char const*) p) [0];
}
inline unsigned get_be16( void const* p )
{
return (unsigned) ((unsigned char const*) p) [0] << 8 |
(unsigned) ((unsigned char const*) p) [1];
}
inline blargg_ulong get_le32( void const* p )
{
return (blargg_ulong) ((unsigned char const*) p) [3] << 24 |
(blargg_ulong) ((unsigned char const*) p) [2] << 16 |
(blargg_ulong) ((unsigned char const*) p) [1] << 8 |
(blargg_ulong) ((unsigned char const*) p) [0];
}
inline blargg_ulong get_be32( void const* p )
{
return (blargg_ulong) ((unsigned char const*) p) [0] << 24 |
(blargg_ulong) ((unsigned char const*) p) [1] << 16 |
(blargg_ulong) ((unsigned char const*) p) [2] << 8 |
(blargg_ulong) ((unsigned char const*) p) [3];
}
inline void set_le16( void* p, unsigned n )
{
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
((unsigned char*) p) [0] = (unsigned char) n;
}
inline void set_be16( void* p, unsigned n )
{
((unsigned char*) p) [0] = (unsigned char) (n >> 8);
((unsigned char*) p) [1] = (unsigned char) n;
}
inline void set_le32( void* p, blargg_ulong n )
{
((unsigned char*) p) [0] = (unsigned char) n;
((unsigned char*) p) [1] = (unsigned char) (n >> 8);
((unsigned char*) p) [2] = (unsigned char) (n >> 16);
((unsigned char*) p) [3] = (unsigned char) (n >> 24);
}
inline void set_be32( void* p, blargg_ulong n )
{
((unsigned char*) p) [3] = (unsigned char) n;
((unsigned char*) p) [2] = (unsigned char) (n >> 8);
((unsigned char*) p) [1] = (unsigned char) (n >> 16);
((unsigned char*) p) [0] = (unsigned char) (n >> 24);
}
#if BLARGG_NONPORTABLE
// Optimized implementation if byte order is known
#if BLARGG_LITTLE_ENDIAN
#define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr))
#define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr))
#define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data))
#define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data))
#elif BLARGG_BIG_ENDIAN
#define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr))
#define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr))
#define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data))
#define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data))
#if BLARGG_CPU_POWERPC
// PowerPC has special byte-reversed instructions
#if defined (__MWERKS__)
#define GET_LE16( addr ) (__lhbrx( addr, 0 ))
#define GET_LE32( addr ) (__lwbrx( addr, 0 ))
#define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 ))
#define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 ))
#elif defined (__GNUC__)
#define GET_LE16( addr ) ({unsigned ppc_lhbrx_; asm( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr), "0" (ppc_lhbrx_) ); ppc_lhbrx_;})
#define GET_LE32( addr ) ({unsigned ppc_lwbrx_; asm( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr), "0" (ppc_lwbrx_) ); ppc_lwbrx_;})
#define SET_LE16( addr, in ) ({asm( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) );})
#define SET_LE32( addr, in ) ({asm( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) );})
#endif
#endif
#endif
#endif
#ifndef GET_LE16
#define GET_LE16( addr ) get_le16( addr )
#define SET_LE16( addr, data ) set_le16( addr, data )
#endif
#ifndef GET_LE32
#define GET_LE32( addr ) get_le32( addr )
#define SET_LE32( addr, data ) set_le32( addr, data )
#endif
#ifndef GET_BE16
#define GET_BE16( addr ) get_be16( addr )
#define SET_BE16( addr, data ) set_be16( addr, data )
#endif
#ifndef GET_BE32
#define GET_BE32( addr ) get_be32( addr )
#define SET_BE32( addr, data ) set_be32( addr, data )
#endif
// auto-selecting versions
inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); }
inline void set_le( BOOST::uint32_t* p, blargg_ulong n ) { SET_LE32( p, n ); }
inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); }
inline void set_be( BOOST::uint32_t* p, blargg_ulong n ) { SET_BE32( p, n ); }
inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); }
inline blargg_ulong get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); }
inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); }
inline blargg_ulong get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); }
#endif

View file

@ -0,0 +1,100 @@
/* Included at the beginning of library source files, after all other #include lines.
Sets up helpful macros and services used in my source code. They don't need
module an annoying module prefix on their names since they are defined after
all other #include lines. */
// snes_spc 0.9.0
#ifndef BLARGG_SOURCE_H
#define BLARGG_SOURCE_H
// If debugging is enabled, abort program if expr is false. Meant for checking
// internal state and consistency. A failed assertion indicates a bug in the module.
// void assert( bool expr );
#include <assert.h>
// If debugging is enabled and expr is false, abort program. Meant for checking
// caller-supplied parameters and operations that are outside the control of the
// module. A failed requirement indicates a bug outside the module.
// void require( bool expr );
#undef require
#define require( expr ) assert( expr )
// Like printf() except output goes to debug log file. Might be defined to do
// nothing (not even evaluate its arguments).
// void dprintf( const char* format, ... );
static inline void blargg_dprintf_( const char*, ... ) { }
#undef dprintf
#define dprintf (1) ? (void) 0 : blargg_dprintf_
// If enabled, evaluate expr and if false, make debug log entry with source file
// and line. Meant for finding situations that should be examined further, but that
// don't indicate a problem. In all cases, execution continues normally.
#undef check
#define check( expr ) ((void) 0)
// If expr yields error string, return it from current function, otherwise continue.
#undef RETURN_ERR
#define RETURN_ERR( expr ) do { \
blargg_err_t blargg_return_err_ = (expr); \
if ( blargg_return_err_ ) return blargg_return_err_; \
} while ( 0 )
// If ptr is 0, return out of memory error string.
#undef CHECK_ALLOC
#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 )
// Avoid any macros which evaluate their arguments multiple times
#undef min
#undef max
#define DEF_MIN_MAX( type ) \
static inline type min( type x, type y ) { if ( x < y ) return x; return y; }\
static inline type max( type x, type y ) { if ( y < x ) return x; return y; }
DEF_MIN_MAX( int )
DEF_MIN_MAX( unsigned )
DEF_MIN_MAX( long )
DEF_MIN_MAX( unsigned long )
DEF_MIN_MAX( float )
DEF_MIN_MAX( double )
#undef DEF_MIN_MAX
/*
// using const references generates crappy code, and I am currenly only using these
// for built-in types, so they take arguments by value
// TODO: remove
inline int min( int x, int y )
template<class T>
inline T min( T x, T y )
{
if ( x < y )
return x;
return y;
}
template<class T>
inline T max( T x, T y )
{
if ( x < y )
return y;
return x;
}
*/
// TODO: good idea? bad idea?
#undef byte
#define byte byte_
typedef unsigned char byte;
// deprecated
#define BLARGG_CHECK_ALLOC CHECK_ALLOC
#define BLARGG_RETURN_ERR RETURN_ERR
// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check
#ifdef BLARGG_SOURCE_BEGIN
#include BLARGG_SOURCE_BEGIN
#endif
#endif

View file

@ -127,7 +127,7 @@ struct DivSample {
unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthBRR, lengthVOX; unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthBRR, lengthVOX;
unsigned int off8, off16, off1, offDPCM, offZ, offQSoundA, offA, offB, offBRR, offVOX; unsigned int off8, off16, off1, offDPCM, offZ, offQSoundA, offA, offB, offBRR, offVOX;
unsigned int offSegaPCM, offQSound, offX1_010, offSU, offYMZ280B, offRF5C68; unsigned int offSegaPCM, offQSound, offX1_010, offSU, offYMZ280B, offRF5C68, offSNES;
unsigned int samples; unsigned int samples;
@ -329,6 +329,7 @@ struct DivSample {
offSU(0), offSU(0),
offYMZ280B(0), offYMZ280B(0),
offRF5C68(0), offRF5C68(0),
offSNES(0),
samples(0) {} samples(0) {}
~DivSample(); ~DivSample();
}; };

View file

@ -142,7 +142,7 @@ const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={
"ADPCM-B", "ADPCM-B",
NULL, NULL,
"8-bit PCM", "8-bit PCM",
NULL, // "BRR", "BRR",
"VOX", "VOX",
NULL, NULL,
NULL, NULL,
@ -942,6 +942,7 @@ const int availableSystems[]={
DIV_SYSTEM_MSM6258, DIV_SYSTEM_MSM6258,
DIV_SYSTEM_MSM6295, DIV_SYSTEM_MSM6295,
DIV_SYSTEM_RF5C68, DIV_SYSTEM_RF5C68,
DIV_SYSTEM_SNES,
DIV_SYSTEM_PCM_DAC, DIV_SYSTEM_PCM_DAC,
0 // don't remove this last one! 0 // don't remove this last one!
}; };

View file

@ -306,6 +306,14 @@ const char* gbHWSeqCmdTypes[6]={
"Loop until Release" "Loop until Release"
}; };
const char* snesGainModes[5]={
"Direct",
"Decrease (linear)",
"Decrease (logarithmic)",
"Increase (linear)",
"Increase (bent line)"
};
// do not change these! // do not change these!
// anything other than a checkbox will look ugly! // anything other than a checkbox will look ugly!
// //
@ -3748,14 +3756,20 @@ void FurnaceGUI::drawInsEdit() {
} }
ImGui::EndCombo(); ImGui::EndCombo();
} }
if (ins->type==DIV_INS_AMIGA) { if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SNES) {
P(ImGui::Checkbox("Use wavetable (Amiga only)",&ins->amiga.useWave)); P(ImGui::Checkbox("Use wavetable (Amiga/SNES only)",&ins->amiga.useWave));
if (ins->amiga.useWave) { if (ins->amiga.useWave) {
int len=ins->amiga.waveLen+1; int len=ins->amiga.waveLen+1;
if (ImGui::InputInt("Width",&len,2,16)) { if (ImGui::InputInt("Width",&len,2,16)) {
if (ins->type==DIV_INS_SNES) {
if (len<16) len=16;
if (len>256) len=256;
ins->amiga.waveLen=(len&(~15))-1;
} else {
if (len<2) len=2; if (len<2) len=2;
if (len>256) len=256; if (len>256) len=256;
ins->amiga.waveLen=(len&(~1))-1; ins->amiga.waveLen=(len&(~1))-1;
}
PARAMETER PARAMETER
} }
} }
@ -4006,9 +4020,9 @@ void FurnaceGUI::drawInsEdit() {
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
} }
if (ins->type==DIV_INS_SNES) if (ImGui::BeginTabItem("SNES")) { // Purposeful Conflict if (ins->type==DIV_INS_SNES) if (ImGui::BeginTabItem("SNES")) {
P(ImGui::Checkbox("Use envelope",&ins->snes.useEnv)); // Purposeful Conflict P(ImGui::Checkbox("Use envelope",&ins->snes.useEnv));
ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); // Purposeful Conflict ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale);
if (ins->snes.useEnv) { if (ins->snes.useEnv) {
if (ImGui::BeginTable("SNESEnvParams",5,ImGuiTableFlags_NoHostExtendX)) { if (ImGui::BeginTable("SNESEnvParams",5,ImGuiTableFlags_NoHostExtendX)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
@ -4049,52 +4063,52 @@ void FurnaceGUI::drawInsEdit() {
ImGui::EndTable(); ImGui::EndTable();
} }
} else { } else {
if (ImGui::BeginTable("SNESGainParams",3,ImGuiTableFlags_NoHostExtendX)) { // Purposeful Conflict if (ImGui::BeginTable("SNESGainParams",3,ImGuiTableFlags_NoHostExtendX)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); // Purposeful Conflict ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); // Purposeful Conflict ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); // Purposeful Conflict ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow(); // Purposeful Conflict ImGui::TableNextRow();
ImGui::TableNextColumn(); // Purposeful Conflict ImGui::TableNextColumn();
CENTER_TEXT("Gain Mode"); // Purposeful Conflict CENTER_TEXT("Gain Mode");
ImGui::TextUnformatted("Gain Mode"); // Purposeful Conflict ImGui::TextUnformatted("Gain Mode");
ImGui::TableNextColumn(); // Purposeful Conflict ImGui::TableNextColumn();
CENTER_TEXT("Gain"); // Purposeful Conflict CENTER_TEXT("Gain");
ImGui::TextUnformatted("Gain"); // Purposeful Conflict ImGui::TextUnformatted("Gain");
ImGui::TableNextColumn(); // Purposeful Conflict ImGui::TableNextColumn();
CENTER_TEXT("Envelope"); // Purposeful Conflict CENTER_TEXT("Envelope");
ImGui::TextUnformatted("Envelope"); // Purposeful Conflict ImGui::TextUnformatted("Envelope");
ImGui::TableNextRow(); // Purposeful Conflict ImGui::TableNextRow();
ImGui::TableNextColumn(); // Purposeful Conflict ImGui::TableNextColumn();
if (ImGui::RadioButton("Direct",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)) { // Purposeful Conflict if (ImGui::RadioButton("Direct",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)) {
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DIRECT; // Purposeful Conflict ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DIRECT;
PARAMETER; // Purposeful Conflict PARAMETER;
} // Purposeful Conflict }
if (ImGui::RadioButton("Decrease (linear)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LINEAR)) { // Purposeful Conflict if (ImGui::RadioButton("Decrease (linear)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LINEAR)) {
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LINEAR; // Purposeful Conflict ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LINEAR;
PARAMETER; // Purposeful Conflict PARAMETER;
} // Purposeful Conflict }
if (ImGui::RadioButton("Decrease (logarithmic)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LOG)) { // Purposeful Conflict if (ImGui::RadioButton("Decrease (logarithmic)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LOG)) {
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LOG; // Purposeful Conflict ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LOG;
PARAMETER; // Purposeful Conflict PARAMETER;
} // Purposeful Conflict }
if (ImGui::RadioButton("Increase (linear)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_INC_LINEAR)) { // Purposeful Conflict if (ImGui::RadioButton("Increase (linear)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_INC_LINEAR)) {
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_INC_LINEAR; // Purposeful Conflict ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_INC_LINEAR;
PARAMETER; // Purposeful Conflict PARAMETER;
} // Purposeful Conflict }
if (ImGui::RadioButton("Increase (bent line)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_INC_INVLOG)) { // Purposeful Conflict if (ImGui::RadioButton("Increase (bent line)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_INC_INVLOG)) {
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_INC_INVLOG; // Purposeful Conflict ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_INC_INVLOG;
PARAMETER; // Purposeful Conflict PARAMETER;
} // Purposeful Conflict }
ImGui::TableNextColumn(); // Purposeful Conflict ImGui::TableNextColumn();
unsigned char gainMax=(ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)?127:31; // Purposeful Conflict unsigned char gainMax=(ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)?127:31;
if (ins->snes.gain>gainMax) ins->snes.gain=gainMax; // Purposeful Conflict if (ins->snes.gain>gainMax) ins->snes.gain=gainMax;
P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax)); // Purposeful Conflict P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax));
ImGui::TableNextColumn(); // Purposeful Conflict ImGui::TableNextColumn();
ImGui::Text("Envelope goes here..."); // Purposeful Conflict ImGui::Text("Envelope goes here...");
ImGui::EndTable(); ImGui::EndTable();
} }
@ -4365,7 +4379,8 @@ void FurnaceGUI::drawInsEdit() {
dutyMax=ins->amiga.useSample?0:8; dutyMax=ins->amiga.useSample?0:8;
} }
if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS || if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS ||
ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS || ins->type==DIV_INS_MULTIPCM) { ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS || ins->type==DIV_INS_MULTIPCM ||
ins->type==DIV_INS_SNES) {
dutyMax=0; dutyMax=0;
} }
if (ins->type==DIV_INS_VERA) { if (ins->type==DIV_INS_VERA) {
@ -4487,6 +4502,10 @@ void FurnaceGUI::drawInsEdit() {
ex1Max=65535; ex1Max=65535;
ex2Max=65535; ex2Max=65535;
} }
if (ins->type==DIV_INS_SNES && !ins->snes.useEnv) {
ex1Max=4;
ex2Max=31;
}
int panMin=0; int panMin=0;
int panMax=0; int panMax=0;
@ -4627,6 +4646,8 @@ void FurnaceGUI::drawInsEdit() {
macroList.push_back(FurnaceGUIMacroDesc("Clock Divider",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("Clock Divider",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} else if (ins->type==DIV_INS_QSOUND) { } else if (ins->type==DIV_INS_QSOUND) {
macroList.push_back(FurnaceGUIMacroDesc("Echo Feedback",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("Echo Feedback",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} else if (ins->type==DIV_INS_SNES) {
macroList.push_back(FurnaceGUIMacroDesc("Gain Mode",&ins->std.ex1Macro,0,ex1Max,64,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,NULL,false,snesGainModes));
} else { } else {
macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} }
@ -4643,7 +4664,9 @@ void FurnaceGUI::drawInsEdit() {
} else if (ins->type==DIV_INS_ES5506) { } else if (ins->type==DIV_INS_ES5506) {
macroList.push_back(FurnaceGUIMacroDesc("Filter K2",&ins->std.ex2Macro,((ins->std.ex2Macro.mode==1)?(-ex2Max):0),ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,macroRelativeMode)); macroList.push_back(FurnaceGUIMacroDesc("Filter K2",&ins->std.ex2Macro,((ins->std.ex2Macro.mode==1)?(-ex2Max):0),ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER],false,macroRelativeMode));
} else if (ins->type==DIV_INS_QSOUND) { } else if (ins->type==DIV_INS_QSOUND) {
macroList.push_back(FurnaceGUIMacroDesc("Echo Buffer Len",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("Echo Length",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} else if (ins->type==DIV_INS_SNES) {
macroList.push_back(FurnaceGUIMacroDesc("Gain Rate",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_VOLUME]));
} else { } else {
macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex2Macro,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,ex2Bit,ayEnvBits)); macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex2Macro,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,ex2Bit,ayEnvBits));
} }

View file

@ -357,6 +357,12 @@ void FurnaceGUI::initSystemPresets() {
0 0
} }
)); ));
cat.systems.push_back(FurnaceGUISysDef(
"SNES", {
DIV_SYSTEM_SNES, 64, 0, 0,
0
}
));
sysCategories.push_back(cat); sysCategories.push_back(cat);
cat=FurnaceGUISysCategory("Wavetable","chips which use user-specified waveforms to generate sound."); cat=FurnaceGUISysCategory("Wavetable","chips which use user-specified waveforms to generate sound.");
@ -644,6 +650,12 @@ void FurnaceGUI::initSystemPresets() {
0 0
} }
)); ));
cat.systems.push_back(FurnaceGUISysDef(
"SNES", {
DIV_SYSTEM_SNES, 64, 0, 0,
0
}
));
cat.systems.push_back(FurnaceGUISysDef( cat.systems.push_back(FurnaceGUISysDef(
"Mattel Intellivision", { "Mattel Intellivision", {
DIV_SYSTEM_AY8910, 64, 0, 48, DIV_SYSTEM_AY8910, 64, 0, 48,

View file

@ -760,6 +760,7 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
case DIV_SYSTEM_BUBSYS_WSG: case DIV_SYSTEM_BUBSYS_WSG:
case DIV_SYSTEM_YMU759: case DIV_SYSTEM_YMU759:
case DIV_SYSTEM_PET: case DIV_SYSTEM_PET:
case DIV_SYSTEM_SNES:
case DIV_SYSTEM_T6W28: case DIV_SYSTEM_T6W28:
ImGui::Text("nothing to configure"); ImGui::Text("nothing to configure");
break; break;