blob: 17b85a8ba76f3aa7d51651f011b5977312230fa8 [file] [log] [blame] [raw]
// Issues:
// - Filter cutoff frequencies not 100% accurate
// - Combined waveforms of the 6581 incorrect (SID card used 8580 anyway)
// - filter distortion not emulated
// - no joystick or paddle support
// - probably many more
#include <math.h>
#include <memory.h>
#include "Sid.h"
#include "Tedmem.h"
#define DIGIBLASTER_MULT 14
#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
#endif
// Hack to store master volume
unsigned int SIDsound::masterVolume = 0;
//
// Random number generator for noise waveform
//
// Test a bit. Returns 1 if bit is set.
inline static long bit(long val, unsigned int bitnr)
{
return (val >> bitnr) & 1;
}
inline void SIDsound::updateShiftReg(SIDVoice &v)
{
unsigned int shiftReg = v.shiftReg;
unsigned int bit22 = bit(shiftReg,22);
unsigned int bit17 = bit(shiftReg,17);
// Shift 1 bit left
shiftReg = ((shiftReg) << 1);// & 0x7fffff;
// Feed bit 0
v.shiftReg = shiftReg | (bit22 ^ bit17);
}
inline int SIDsound::waveNoise(SIDVoice &v)
{
unsigned int shiftReg = v.shiftReg;
// Pick out bits to make output value, left shift by 4
return
(bit(shiftReg,22) << 11) |
(bit(shiftReg,20) << 10) |
(bit(shiftReg,16) << 9) |
(bit(shiftReg,13) << 8) |
(bit(shiftReg,11) << 7) |
(bit(shiftReg, 7) << 6) |
(bit(shiftReg, 4) << 5) |
(bit(shiftReg, 2) << 4);
};
void SIDsound::setModel(unsigned int model)
{
int i;
switch (model) {
case SID8580DB:
case SID8580:
for ( i=0; i<2048; i++) {
double x = i / 8.0;
//double cf = 12500.0 * i / 2048.0; // specs and YAPE
// approximate with a 3-degree polynomial
//double cf = 0.0003*x*x*x + 0.0882*x*x + 44.49*x - 38.409;
// approximate with a 2-degree polynomial
//double cf = -0.0177*x*x + 55.261*x - 55.518; // CSG 8580R4
double cf = -0.0156*x*x + 48.473*x - 45.074; // 8580R5
cutOffFreq[i] = cf <= 0 ? 0 : cf;
}
dcWave = 0x800;
dcMixer = 0;
dcVoice = 0;
break;
case SID6581: // R4 actually
for (i=0; i<1024; i++) {
cutOffFreq[i] = (tanh(((double)i/1.5 - 1024.0)/1024.0*M_PI) + tanh(M_PI))
* (6000.0 - 220.0) + 220.0;
}
for (; i<1056; i++) {
double x = ((double)i - 1024.0) / (1056.0 - 1003.);
cutOffFreq[i] = x*(1315.0 - 1003.0) + 1003.0;
}
for (; i<2048; i++) {
double x = ((double)i - 1056.0) / (2048.0 - 1056.0);
cutOffFreq[i] = //(tanh (((double)i - 2048.0)/1024.0*M_PI) + tanh(M_PI))
//* (20163.0 - 1315.0) + 1315.0;
(20163.0 - 1315.0) * x + 1315.0;
}
dcWave = 0x380;
dcMixer = -0xFFF*0xFF/18 >> 7;
dcVoice = 0x800*0xFF;
break;
case SID6581R1: // 6581 R1
for (i=0; i<1024; i++) {
cutOffFreq[i] = (tanh(((double)i-1024.0)/1024.0*M_PI) + tanh(M_PI))
* (6000.0 - 220.0) + 220.0;
}
for (; i<2048; i++) {
cutOffFreq[i] = (tanh (((double)i-2048.0)/1024.0*M_PI) + tanh(M_PI))
* (18000.0 - 4600.0) + 4600.0;
}
dcWave = 0x380;
dcMixer = -0xFFF*0xFF/18 >> 7;
dcVoice = 0x800*0xFF;
break;
}
setFilterCutoff();
model_ = model;
}
// Static data members
const unsigned int SIDsound::RateCountPeriod[16] = {
0x7F00,0x0006,0x003C,0x0330,0x20C0,0x6755,0x3800,0x500E,
0x1212,0x0222,0x1848,0x59B8,0x3840,0x77E2,0x7625,0x0A93
};
const unsigned char SIDsound::envGenDRdivisors[256] = {
30,30,30,30,30,30,16,16,16,16,16,16,16,16,8,8,
8,8,8,8,8,8,8,8,8,8,4,4,4,4,4,4,
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
4,4,4,4,4,4,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
};
void SIDsound::calcEnvelopeTable()
{
// number of SIDsound envelope clocks per sample (0x1FFFFF)
const double deltaSampleCyclesFloat = ((double) sidBaseFreq * 256.0) / (double)sampleRate;
sidCyclesPerSampleInt = (unsigned int) (deltaSampleCyclesFloat + 0.5);
}
void SIDsound::setFrequency(unsigned int sid_frequency)
{
switch (sid_frequency) {
case 0:
sidBaseFreq = TED_SOUND_CLOCK * 4; // 312 * 114 * 50 / 2;
break;
default:
sidBaseFreq = SOUND_FREQ_PAL_C64;
break;
}
calcEnvelopeTable();
}
void SIDsound::setSampleRate(unsigned int sampleRate_)
{
sampleRate = sampleRate_;
calcEnvelopeTable();
}
SIDsound::SIDsound(unsigned int model, unsigned int chnlDisableMask) : enableDigiBlaster(false)
{
unsigned int i;
// Link voices together
for (i=0; i<3; i++) {
voice[i].modulatedBy = &voice[(i+2)%3]; // previous voice
voice[i].modulatesThis = &voice[(i+1)%3]; // next voice
voice[i].disabled = !!((chnlDisableMask >> i) & 1);
}
filterCutoff = 0;
setModel(model);
setFrequency(0);
reset();
}
void SIDsound::reset(void)
{
volume = masterVolume;
lastByteWritten = 0;
for (int v=0; v<3; v++) {
voice[v].wave = WAVE_NONE;
voice[v].egState = EG_FROZEN;
voice[v].accu = voice[v].add = 0;
voice[v].freq = voice[v].pw = 0;
voice[v].envCurrLevel = voice[v].envSustainLevel = 0;
voice[v].gate = voice[v].ring = voice[v].test = 0;
voice[v].filter = voice[v].sync = false;
voice[v].muted = 0;
// Initial value of internal shift register
voice[v].shiftReg = 0x7FFFFC;
voice[v].envExpCounter = 0;
voice[v].envAttackAdd = voice[v].envDecaySub = voice[v].envReleaseSub = 0;
voice[v].envCounterCompare = 0;
voice[v].envCounter = 0x7fff;
}
filterType = FILTER_NONE;
filterCutoff = filterResonance = 0;
Vhp = Vbp = Vlp = 0;
setFilterCutoff();
setResonance();
dcDigiBlaster = 0;
clockDeltaRemainder = 0;
}
inline int SIDsound::getWaveSample(SIDVoice &v)
{
switch (v.wave) {
case WAVE_TRI:
return waveTriangle(v);
case WAVE_SAW:
return waveSaw(v);
case WAVE_PULSE:
return wavePulse(v);
case WAVE_TRISAW:
return waveTriSaw(v);
case WAVE_TRIPULSE:
return waveTriPulse(v);
case WAVE_SAWPULSE:
return waveSawPulse(v);
case WAVE_TRISAWPULSE:
return waveTriSawPulse(v);
case WAVE_NOISE:
return waveNoise(v);
default:
return 0x800;
}
}
unsigned char SIDsound::read(unsigned int adr)
{
switch(adr) {
case 0x19:
case 0x1A:
// POTX/POTY paddle AD converters (unemulated)
lastByteWritten = 0;
return 0xFF;
// Voice 3 (only) oscillator readout
case 0x1B:
lastByteWritten = 0;
return (unsigned char)(getWaveSample(voice[2])>>0); // 4?
// Voice 3 EG readout
case 0x1C:
return (unsigned char)(voice[2].envCurrLevel);
case 0x1E: // Digiblaster DAC readout
if (enableDigiBlaster && model_ == SID8580)
{
return (unsigned char) (dcDigiBlaster >> DIGIBLASTER_MULT);
}
return lastByteWritten;
default:
// Write-only registers return the last value written
return lastByteWritten;
}
}
void SIDsound::write(unsigned int adr, unsigned char value)
{
lastByteWritten = value;
SIDVoice &v = voice[adr/7];
switch (adr) {
case 0:
case 7:
case 14:
v.freq = (unsigned short)((v.freq & 0xff00) | value);
v.add = (unsigned int)(((double)v.freq
* sidBaseFreq) * 16.0 / sampleRate + 0.5);
break;
case 1:
case 8:
case 15:
v.freq = (unsigned short)((v.freq & 0xff) | (value << 8));
v.add = (unsigned int)(((double)v.freq
* sidBaseFreq) * 16.0 / sampleRate + 0.5);
break;
case 2:
case 9:
case 16:
v.pw = (unsigned short)((v.pw & 0x0f00) | value);
break;
case 3:
case 10:
case 17:
v.pw = (unsigned short)((v.pw & 0xff) | ((value & 0xf) << 8));
break;
case 4:
case 11:
case 18:
if ((value & 1) != (unsigned char) v.gate) {
if (value & 1) {
// gate on
v.egState = EG_ATTACK;
v.envCounterCompare = v.envAttackAdd;
} else {
// gate off
#if 00
if (v.egState != EG_FROZEN)
#endif
v.egState = EG_RELEASE;
v.envCounterCompare = v.envReleaseSub;
}
v.gate = value & 1;
}
v.modulatedBy->sync = value & 2;
v.ring = value & 4;
if ((value & 8) && !v.test) {
v.accu = 0; //(model_ >= SID8580) ? 0 : 0;
unsigned int bit19 = (v.shiftReg >> 19) & 1;
v.shiftReg = (v.shiftReg & 0x7ffffd) | ((bit19^1) << 1);
v.test = 0xFFF;
} else if (v.test && !(value & 8)) {
unsigned int bit0 = ((v.shiftReg >> 22) ^ (v.shiftReg >> 17)) & 0x1;
v.shiftReg <<= 1;
v.shiftReg &= 0x7fffff;
v.shiftReg |= bit0;
v.test = 0x000;
}
v.wave = (value >> 4) & 0x0F;
if (v.wave > 8) {
v.shiftReg &= 0x7fffff^(1<<22)^(1<<20)^(1<<16)^(1<<13)^(1<<11)^(1<<7)^(1<<4)^(1<<2);
}
break;
case 5:
case 12:
case 19:
v.envAttackAdd = value >> 4;
v.envDecaySub = value & 0x0F;
if (v.egState == EG_ATTACK)
v.envCounterCompare = v.envAttackAdd;
else if (v.egState == EG_DECAY)
v.envCounterCompare = v.envDecaySub;
break;
case 6:
case 13:
case 20:
v.envSustainLevel = (value >> 4) * 0x11;
v.envReleaseSub = value & 0x0F;
if (v.egState == EG_RELEASE)
v.envCounterCompare = v.envReleaseSub;
break;
case 21:
if ((unsigned int)(value&7) != (filterCutoff&7)) {
filterCutoff = (value&7)|(filterCutoff&0x7F8);
setFilterCutoff();
}
break;
case 22:
filterCutoff = (value<<3)|(filterCutoff&7);
setFilterCutoff();
break;
case 23:
voice[0].filter = value & 1;
voice[1].filter = value & 2;
voice[2].filter = value & 4;
filterResonance = (unsigned char)(value >> 4);
setResonance();
break;
case 24:
volume = value & 0x0F;
voice[2].muted = value & 0x80;
filterType = (unsigned char)((value >> 4) & 7);
break;
case 30: // Digiblaster DAC
if (enableDigiBlaster && model_ == SID8580)
{
dcDigiBlaster = (value ^ 0x00) << DIGIBLASTER_MULT;
}
break;
case 31: // Digiblaster ADC
break;
}
}
inline void SIDsound::setFilterCutoff()
{
const double freqDomainDivCoeff = 2 * M_PI * 1.048576;
w0 = int(cutOffFreq[filterCutoff] * freqDomainDivCoeff);
// Limit cutoff to Nyquist frq to keep the sample based filter stable
const double NyquistFrq = double(sampleRate) / 2;
const double maxCutOff = NyquistFrq > 16000.0 ? 16000.0 : NyquistFrq;
const int w0MaxDt = int(maxCutOff * freqDomainDivCoeff); // 16000
if (w0 > w0MaxDt) w0 = w0MaxDt;
}
inline void SIDsound::setResonance()
{
resonanceCoeffDiv1024 = (int) (1024.0/(0.707 + 1.9 * (double) filterResonance / 15.0) + 0.5); // 2.3
}
inline unsigned int SIDsound::clock()
{
unsigned int count = sidCyclesPerSampleInt >> 8;
unsigned int tmp = sidCyclesPerSampleInt & 0xFF;
unsigned int newCount = clockDeltaRemainder + tmp;
if (newCount >= 0x100) {
clockDeltaRemainder = newCount & 0xFF;
count++;
} else {
clockDeltaRemainder = newCount;
}
return count;
}
// simplified version of http://bel.fi/~alankila/c64-sw/index-cpp.html
inline int SIDsound::filterOutput(unsigned int cycles, int Vi)
{
int w0deltaTime = w0 >> 6;
Vi >>= 7;
unsigned int count = cycles;
do {
int dVlp = (w0deltaTime * Vbp >> 14);
Vlp -= dVlp;
int dVbp = (w0deltaTime * Vhp >> 14);
Vbp -= dVbp;
Vhp = (Vbp * resonanceCoeffDiv1024 >> 10) - Vlp - Vi;
} while (--count);
int Vf;
switch (filterType) {
default:
case FILTER_NONE:
Vf = 0;
break;
case FILTER_LP:
Vf = Vlp;
break;
case FILTER_BP:
Vf = Vbp;
break;
case FILTER_LPBP:
Vf = Vlp + Vbp;
break;
case FILTER_HP:
Vf = Vhp;
break;
case FILTER_NOTCH:
Vf = Vlp + Vhp;
break;
case FILTER_HPBP:
Vf = Vbp + Vhp;
break;
case FILTER_ALL:
Vf = Vlp + Vbp + Vhp;
break;
}
return Vf << 7;
}
// Envelope based on:
// http://blog.kevtris.org/?p=13
inline int SIDsound::doEnvelopeGenerator(unsigned int cycles, SIDVoice &v)
{
unsigned int count = cycles;
do {
unsigned int LFSR = v.envCounter;
if (LFSR != RateCountPeriod[v.envCounterCompare]) {
const unsigned int feedback = ((LFSR >> 14) ^ (LFSR >> 13)) & 1;
LFSR = ((LFSR << 1) | feedback) & 0x7FFF;
v.envCounter = LFSR;
} else {
// LFSR = 0x7fff reset LFSR
v.envCounter = 0x7fff;
if (v.egState == EG_ATTACK || ++v.envExpCounter == envGenDRdivisors[v.envCurrLevel]) {
v.envExpCounter = 0;
switch (v.egState) {
case EG_ATTACK:
// According to Bob Yannes, Attack is linear...
if ( ((++v.envCurrLevel) & 0xFF) == 0xFF) {
v.egState = EG_DECAY;
v.envCounterCompare = v.envDecaySub;
}
break;
case EG_DECAY:
if (v.envCurrLevel != v.envSustainLevel) {
--v.envCurrLevel &= 0xFF;
if (!v.envCurrLevel)
v.egState = EG_FROZEN;
}
break;
case EG_RELEASE:
v.envCurrLevel = (v.envCurrLevel - 1) & 0xFF;
if (!v.envCurrLevel)
v.egState = EG_FROZEN;
break;
case EG_FROZEN:
v.envCurrLevel = 0;
break;
}
}
}
} while (--count);
return v.envCurrLevel & 0xFF; // envelope is 8 bits
}
void SIDsound::calcSamples(short *buf, long accu)
{
for (;accu--;) {
// Outputs for normal and filtered sounds
int sumFilteredOutput = 0;
int sumOutput = 0;
int sample;
const unsigned int cyclesToDo = clock();
// Loop for the three voices
int j = 2;
do {
SIDVoice &v = voice[j];
int envelope = doEnvelopeGenerator(cyclesToDo, v);
// Waveform generator
if (!v.test) {
#if 1
unsigned int accPrev = v.accu;
// Update accumulator
v.accu += v.add;
// FIXME Apply ring modulation.
if (v.sync && !(accPrev & 0x8000000) && (v.accu & 0x8000000)
)
#else
v.accPrev = v.accu;
// Update accumulator if test bit not set
v.accu += v.add;
unsigned int accPrev = v.accPrev;
if (v.sync && !(v.accPrev & 0x8000000) && (v.accu & 0x8000000)
&& !( v.modulatedBy->sync && !(v.modulatedBy->accPrev & 0x800000) &&
(v.modulatedBy->accu & 0x800000))
)
#endif
v.modulatesThis->accu = 0;
if (v.freq) {
unsigned int accNext = accPrev;
unsigned int freq = v.freq << 4;
do {
accNext += freq;
// noise shift register is updating even when waveform is not selected
if (!(accPrev & 0x0800000) && (accNext & 0x0800000))
updateShiftReg(v);
accPrev = accNext;
} while ( accNext < v.accu );
}
// real accu is 24 bit but we use FP integer arithmetic
v.accu &= 0xFFFFFFF;
}
int output = v.disabled ? 0x0800 : getWaveSample(v);
if (v.filter)
sumFilteredOutput += (output - dcWave) * envelope + dcVoice;
else {
if (v.muted)
sumOutput += (0x0800 - dcWave) * envelope + dcVoice;
else
sumOutput += (output - dcWave) * envelope + dcVoice;
}
} while (j--);
int accu = (sumOutput + filterOutput(cyclesToDo, sumFilteredOutput)
+ dcMixer + dcDigiBlaster) * volume;
#if 1
sample = accu >> 12;
#else
unsigned int interPolationFac = (clockDeltaRemainder - sidCyclesPerSampleInt) & 0xFF;
accu >>= 7;
sample = (prevAccu * (0xFF ^ interPolationFac) + accu * (interPolationFac)) >> 12;
prevAccu = accu;
#endif
*buf++ = (short) sample;
}
}
SIDsound::~SIDsound()
{
masterVolume = volume;
}