mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-26 22:43:01 +00:00
Merge branch 'snes' of https://github.com/akumanatt/furnace into akumanatt-snes
This commit is contained in:
commit
c0de45ff0c
19 changed files with 2616 additions and 70 deletions
|
@ -437,6 +437,8 @@ src/engine/platform/sound/rf5c68.cpp
|
|||
|
||||
src/engine/platform/sound/oki/okim6258.cpp
|
||||
|
||||
src/engine/platform/sound/snes/SPC_DSP.cpp
|
||||
|
||||
src/engine/platform/oplAInterface.cpp
|
||||
src/engine/platform/ym2608Interface.cpp
|
||||
src/engine/platform/ym2610Interface.cpp
|
||||
|
@ -509,6 +511,7 @@ src/engine/platform/scc.cpp
|
|||
src/engine/platform/ymz280b.cpp
|
||||
src/engine/platform/namcowsg.cpp
|
||||
src/engine/platform/rf5c68.cpp
|
||||
src/engine/platform/snes.cpp
|
||||
src/engine/platform/pcmdac.cpp
|
||||
src/engine/platform/dummy.cpp
|
||||
)
|
||||
|
|
|
@ -36,5 +36,6 @@ this is a list of systems that Furnace supports, including each system's effects
|
|||
- [Konami VRC6](vrc6.md)
|
||||
- [Famicom Disk System](fds.md)
|
||||
- [Nintendo MMC5](mmc5.md)
|
||||
- [SNES](snes.md)
|
||||
|
||||
Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but...
|
||||
|
|
18
papers/doc/7-systems/snes.md
Normal file
18
papers/doc/7-systems/snes.md
Normal 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!
|
|
@ -65,6 +65,7 @@
|
|||
#include "platform/scc.h"
|
||||
#include "platform/ymz280b.h"
|
||||
#include "platform/rf5c68.h"
|
||||
#include "platform/snes.h"
|
||||
#include "platform/pcmdac.h"
|
||||
#include "platform/dummy.h"
|
||||
#include "../ta-log.h"
|
||||
|
@ -398,6 +399,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
dispatch=new DivPlatformNamcoWSG;
|
||||
((DivPlatformNamcoWSG*)dispatch)->setDeviceType(30);
|
||||
break;
|
||||
case DIV_SYSTEM_SNES:
|
||||
dispatch=new DivPlatformSNES;
|
||||
break;
|
||||
case DIV_SYSTEM_PCM_DAC:
|
||||
dispatch=new DivPlatformPCMDAC;
|
||||
break;
|
||||
|
|
|
@ -559,9 +559,10 @@ void DivInstrument::putInsData(SafeWriter* w) {
|
|||
w->writeC(es5506.envelope.k2Slow);
|
||||
|
||||
// SNES
|
||||
// @tildearrow please update this
|
||||
w->writeC(snes.useEnv);
|
||||
w->writeC(snes.gainMode);
|
||||
w->writeC(snes.gain);
|
||||
w->writeC(0);
|
||||
w->writeC(0);
|
||||
w->writeC(snes.a);
|
||||
w->writeC(snes.d);
|
||||
w->writeC(snes.s);
|
||||
|
@ -1258,8 +1259,8 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
|
|||
// SNES
|
||||
if (version>=109) {
|
||||
snes.useEnv=reader.readC();
|
||||
snes.gainMode=(DivInstrumentSNES::GainMode)reader.readC();
|
||||
snes.gain=reader.readC();
|
||||
reader.readC();
|
||||
reader.readC();
|
||||
snes.a=reader.readC();
|
||||
snes.d=reader.readC();
|
||||
snes.s=reader.readC();
|
||||
|
|
|
@ -520,21 +520,21 @@ struct DivInstrumentES5506 {
|
|||
};
|
||||
|
||||
struct DivInstrumentSNES {
|
||||
enum GainMode: unsigned char { // Purposeful Conflict
|
||||
GAIN_MODE_DIRECT=0, // Purposeful Conflict
|
||||
GAIN_MODE_DEC_LINEAR=4, // Purposeful Conflict
|
||||
GAIN_MODE_DEC_LOG=5, // Purposeful Conflict
|
||||
GAIN_MODE_INC_LINEAR=6, // Purposeful Conflict
|
||||
GAIN_MODE_INC_INVLOG=7 // Purposeful Conflict
|
||||
enum GainMode: unsigned char {
|
||||
GAIN_MODE_DIRECT=0,
|
||||
GAIN_MODE_DEC_LINEAR=4,
|
||||
GAIN_MODE_DEC_LOG=5,
|
||||
GAIN_MODE_INC_LINEAR=6,
|
||||
GAIN_MODE_INC_INVLOG=7
|
||||
};
|
||||
bool useEnv;
|
||||
GainMode gainMode; // Purposeful Conflict
|
||||
unsigned char gain; // Purposeful Conflict
|
||||
GainMode gainMode;
|
||||
unsigned char gain;
|
||||
unsigned char a, d, s, r;
|
||||
DivInstrumentSNES():
|
||||
useEnv(true),
|
||||
gainMode(GAIN_MODE_DIRECT), // Purposeful Conflict
|
||||
gain(127), // Purposeful Conflict
|
||||
gainMode(GAIN_MODE_DIRECT),
|
||||
gain(127),
|
||||
a(15),
|
||||
d(7),
|
||||
s(7),
|
||||
|
|
518
src/engine/platform/snes.cpp
Normal file
518
src/engine/platform/snes.cpp
Normal 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
112
src/engine/platform/snes.h
Normal 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
|
1032
src/engine/platform/sound/snes/SPC_DSP.cpp
Normal file
1032
src/engine/platform/sound/snes/SPC_DSP.cpp
Normal file
File diff suppressed because it is too large
Load diff
322
src/engine/platform/sound/snes/SPC_DSP.h
Normal file
322
src/engine/platform/sound/snes/SPC_DSP.h
Normal 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
|
186
src/engine/platform/sound/snes/blargg_common.h
Normal file
186
src/engine/platform/sound/snes/blargg_common.h
Normal 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
|
26
src/engine/platform/sound/snes/blargg_config.h
Normal file
26
src/engine/platform/sound/snes/blargg_config.h
Normal 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
|
185
src/engine/platform/sound/snes/blargg_endian.h
Normal file
185
src/engine/platform/sound/snes/blargg_endian.h
Normal 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
|
100
src/engine/platform/sound/snes/blargg_source.h
Normal file
100
src/engine/platform/sound/snes/blargg_source.h
Normal 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
|
|
@ -127,7 +127,7 @@ struct DivSample {
|
|||
|
||||
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 offSegaPCM, offQSound, offX1_010, offSU, offYMZ280B, offRF5C68;
|
||||
unsigned int offSegaPCM, offQSound, offX1_010, offSU, offYMZ280B, offRF5C68, offSNES;
|
||||
|
||||
unsigned int samples;
|
||||
|
||||
|
@ -329,6 +329,7 @@ struct DivSample {
|
|||
offSU(0),
|
||||
offYMZ280B(0),
|
||||
offRF5C68(0),
|
||||
offSNES(0),
|
||||
samples(0) {}
|
||||
~DivSample();
|
||||
};
|
||||
|
|
|
@ -142,7 +142,7 @@ const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={
|
|||
"ADPCM-B",
|
||||
NULL,
|
||||
"8-bit PCM",
|
||||
NULL, // "BRR",
|
||||
"BRR",
|
||||
"VOX",
|
||||
NULL,
|
||||
NULL,
|
||||
|
@ -942,6 +942,7 @@ const int availableSystems[]={
|
|||
DIV_SYSTEM_MSM6258,
|
||||
DIV_SYSTEM_MSM6295,
|
||||
DIV_SYSTEM_RF5C68,
|
||||
DIV_SYSTEM_SNES,
|
||||
DIV_SYSTEM_PCM_DAC,
|
||||
0 // don't remove this last one!
|
||||
};
|
||||
|
|
|
@ -306,6 +306,14 @@ const char* gbHWSeqCmdTypes[6]={
|
|||
"Loop until Release"
|
||||
};
|
||||
|
||||
const char* snesGainModes[5]={
|
||||
"Direct",
|
||||
"Decrease (linear)",
|
||||
"Decrease (logarithmic)",
|
||||
"Increase (linear)",
|
||||
"Increase (bent line)"
|
||||
};
|
||||
|
||||
// do not change these!
|
||||
// anything other than a checkbox will look ugly!
|
||||
//
|
||||
|
@ -3748,14 +3756,20 @@ void FurnaceGUI::drawInsEdit() {
|
|||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
P(ImGui::Checkbox("Use wavetable (Amiga only)",&ins->amiga.useWave));
|
||||
if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SNES) {
|
||||
P(ImGui::Checkbox("Use wavetable (Amiga/SNES only)",&ins->amiga.useWave));
|
||||
if (ins->amiga.useWave) {
|
||||
int len=ins->amiga.waveLen+1;
|
||||
if (ImGui::InputInt("Width",&len,2,16)) {
|
||||
if (len<2) len=2;
|
||||
if (len>256) len=256;
|
||||
ins->amiga.waveLen=(len&(~1))-1;
|
||||
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>256) len=256;
|
||||
ins->amiga.waveLen=(len&(~1))-1;
|
||||
}
|
||||
PARAMETER
|
||||
}
|
||||
}
|
||||
|
@ -4006,9 +4020,9 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
if (ins->type==DIV_INS_SNES) if (ImGui::BeginTabItem("SNES")) { // Purposeful Conflict
|
||||
P(ImGui::Checkbox("Use envelope",&ins->snes.useEnv)); // Purposeful Conflict
|
||||
ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); // Purposeful Conflict
|
||||
if (ins->type==DIV_INS_SNES) if (ImGui::BeginTabItem("SNES")) {
|
||||
P(ImGui::Checkbox("Use envelope",&ins->snes.useEnv));
|
||||
ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale);
|
||||
if (ins->snes.useEnv) {
|
||||
if (ImGui::BeginTable("SNESEnvParams",5,ImGuiTableFlags_NoHostExtendX)) {
|
||||
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
|
||||
|
@ -4049,52 +4063,52 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::EndTable();
|
||||
}
|
||||
} else {
|
||||
if (ImGui::BeginTable("SNESGainParams",3,ImGuiTableFlags_NoHostExtendX)) { // Purposeful Conflict
|
||||
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); // Purposeful Conflict
|
||||
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); // Purposeful Conflict
|
||||
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); // Purposeful Conflict
|
||||
if (ImGui::BeginTable("SNESGainParams",3,ImGuiTableFlags_NoHostExtendX)) {
|
||||
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
|
||||
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,sliderSize.x);
|
||||
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextRow(); // Purposeful Conflict
|
||||
ImGui::TableNextColumn(); // Purposeful Conflict
|
||||
CENTER_TEXT("Gain Mode"); // Purposeful Conflict
|
||||
ImGui::TextUnformatted("Gain Mode"); // Purposeful Conflict
|
||||
ImGui::TableNextColumn(); // Purposeful Conflict
|
||||
CENTER_TEXT("Gain"); // Purposeful Conflict
|
||||
ImGui::TextUnformatted("Gain"); // Purposeful Conflict
|
||||
ImGui::TableNextColumn(); // Purposeful Conflict
|
||||
CENTER_TEXT("Envelope"); // Purposeful Conflict
|
||||
ImGui::TextUnformatted("Envelope"); // Purposeful Conflict
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
CENTER_TEXT("Gain Mode");
|
||||
ImGui::TextUnformatted("Gain Mode");
|
||||
ImGui::TableNextColumn();
|
||||
CENTER_TEXT("Gain");
|
||||
ImGui::TextUnformatted("Gain");
|
||||
ImGui::TableNextColumn();
|
||||
CENTER_TEXT("Envelope");
|
||||
ImGui::TextUnformatted("Envelope");
|
||||
|
||||
ImGui::TableNextRow(); // Purposeful Conflict
|
||||
ImGui::TableNextColumn(); // Purposeful Conflict
|
||||
if (ImGui::RadioButton("Direct",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)) { // Purposeful Conflict
|
||||
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DIRECT; // Purposeful Conflict
|
||||
PARAMETER; // Purposeful Conflict
|
||||
} // Purposeful Conflict
|
||||
if (ImGui::RadioButton("Decrease (linear)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LINEAR)) { // Purposeful Conflict
|
||||
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LINEAR; // Purposeful Conflict
|
||||
PARAMETER; // Purposeful Conflict
|
||||
} // Purposeful Conflict
|
||||
if (ImGui::RadioButton("Decrease (logarithmic)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LOG)) { // Purposeful Conflict
|
||||
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LOG; // Purposeful Conflict
|
||||
PARAMETER; // Purposeful Conflict
|
||||
} // Purposeful Conflict
|
||||
if (ImGui::RadioButton("Increase (linear)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_INC_LINEAR)) { // Purposeful Conflict
|
||||
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_INC_LINEAR; // Purposeful Conflict
|
||||
PARAMETER; // Purposeful Conflict
|
||||
} // Purposeful Conflict
|
||||
if (ImGui::RadioButton("Increase (bent line)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_INC_INVLOG)) { // Purposeful Conflict
|
||||
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_INC_INVLOG; // Purposeful Conflict
|
||||
PARAMETER; // Purposeful Conflict
|
||||
} // Purposeful Conflict
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::RadioButton("Direct",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)) {
|
||||
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DIRECT;
|
||||
PARAMETER;
|
||||
}
|
||||
if (ImGui::RadioButton("Decrease (linear)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LINEAR)) {
|
||||
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LINEAR;
|
||||
PARAMETER;
|
||||
}
|
||||
if (ImGui::RadioButton("Decrease (logarithmic)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DEC_LOG)) {
|
||||
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LOG;
|
||||
PARAMETER;
|
||||
}
|
||||
if (ImGui::RadioButton("Increase (linear)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_INC_LINEAR)) {
|
||||
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_INC_LINEAR;
|
||||
PARAMETER;
|
||||
}
|
||||
if (ImGui::RadioButton("Increase (bent line)",ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_INC_INVLOG)) {
|
||||
ins->snes.gainMode=DivInstrumentSNES::GAIN_MODE_INC_INVLOG;
|
||||
PARAMETER;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn(); // Purposeful Conflict
|
||||
unsigned char gainMax=(ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)?127:31; // Purposeful Conflict
|
||||
if (ins->snes.gain>gainMax) ins->snes.gain=gainMax; // Purposeful Conflict
|
||||
P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax)); // Purposeful Conflict
|
||||
ImGui::TableNextColumn();
|
||||
unsigned char gainMax=(ins->snes.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT)?127:31;
|
||||
if (ins->snes.gain>gainMax) ins->snes.gain=gainMax;
|
||||
P(CWVSliderScalar("##Gain",sliderSize,ImGuiDataType_U8,&ins->snes.gain,&_ZERO,&gainMax));
|
||||
|
||||
ImGui::TableNextColumn(); // Purposeful Conflict
|
||||
ImGui::Text("Envelope goes here..."); // Purposeful Conflict
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("Envelope goes here...");
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
@ -4365,7 +4379,8 @@ void FurnaceGUI::drawInsEdit() {
|
|||
dutyMax=ins->amiga.useSample?0:8;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (ins->type==DIV_INS_VERA) {
|
||||
|
@ -4435,7 +4450,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
if (ins->type==DIV_INS_VRC6) {
|
||||
waveMax=ins->amiga.useSample?(MAX(1,e->song.waveLen-1)):0;
|
||||
}
|
||||
|
||||
|
||||
if (ins->type==DIV_INS_OPLL) {
|
||||
waveLabel="Patch";
|
||||
}
|
||||
|
@ -4487,6 +4502,10 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ex1Max=65535;
|
||||
ex2Max=65535;
|
||||
}
|
||||
if (ins->type==DIV_INS_SNES && !ins->snes.useEnv) {
|
||||
ex1Max=4;
|
||||
ex2Max=31;
|
||||
}
|
||||
|
||||
int panMin=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]));
|
||||
} else if (ins->type==DIV_INS_QSOUND) {
|
||||
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 {
|
||||
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) {
|
||||
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) {
|
||||
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 {
|
||||
macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex2Macro,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,ex2Bit,ayEnvBits));
|
||||
}
|
||||
|
|
|
@ -356,7 +356,13 @@ void FurnaceGUI::initSystemPresets() {
|
|||
DIV_SYSTEM_MSM6295, 64, 0, 0,
|
||||
0
|
||||
}
|
||||
));
|
||||
));
|
||||
cat.systems.push_back(FurnaceGUISysDef(
|
||||
"SNES", {
|
||||
DIV_SYSTEM_SNES, 64, 0, 0,
|
||||
0
|
||||
}
|
||||
));
|
||||
sysCategories.push_back(cat);
|
||||
|
||||
cat=FurnaceGUISysCategory("Wavetable","chips which use user-specified waveforms to generate sound.");
|
||||
|
@ -644,6 +650,12 @@ void FurnaceGUI::initSystemPresets() {
|
|||
0
|
||||
}
|
||||
));
|
||||
cat.systems.push_back(FurnaceGUISysDef(
|
||||
"SNES", {
|
||||
DIV_SYSTEM_SNES, 64, 0, 0,
|
||||
0
|
||||
}
|
||||
));
|
||||
cat.systems.push_back(FurnaceGUISysDef(
|
||||
"Mattel Intellivision", {
|
||||
DIV_SYSTEM_AY8910, 64, 0, 48,
|
||||
|
|
|
@ -760,6 +760,7 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
|
|||
case DIV_SYSTEM_BUBSYS_WSG:
|
||||
case DIV_SYSTEM_YMU759:
|
||||
case DIV_SYSTEM_PET:
|
||||
case DIV_SYSTEM_SNES:
|
||||
case DIV_SYSTEM_T6W28:
|
||||
ImGui::Text("nothing to configure");
|
||||
break;
|
||||
|
|
Loading…
Reference in a new issue