| // 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" |
| #include "sound.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 = 0x000; |
| 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])>>4); // 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 & 0x0f]) { |
| 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 count) |
| { |
| do { |
| // 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; |
| } while (--count); |
| } |
| |
| void SIDsound::calcSamplesLQ(short *buf, long count) |
| { |
| do { |
| static int sample_freq_cnt = sampleRate; |
| int last_sample = 0; |
| int i = 0; |
| |
| do { |
| short new_sample; |
| sample_freq_cnt -= sampleRate; |
| calcSamples(&new_sample, 1); |
| i++; |
| last_sample += new_sample; |
| } while (sample_freq_cnt > 0); |
| sample_freq_cnt += sampleRate; |
| *buf++ = last_sample / i; |
| i = 0; |
| } while (--count); |
| } |
| |
| SIDsound::~SIDsound() |
| { |
| masterVolume = volume; |
| } |