using System; using BizHawk.Common; namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS { public sealed partial class Sid { /* Commodore SID 6581/8580 core. Many thanks to: - Michael Huth for die shots of the 6569R3 chip (to get ideas how to implement) http://mail.lipsia.de/~enigma/neu/6581.html - Kevtris for figuring out ADSR tables http://blog.kevtris.org/?p=13 - Mixer for a lot of useful SID info http://www.sid.fi/sidwiki/doku.php?id=sid-knowledge - Documentation collected by the libsidplayfp team https://sourceforge.net/projects/sidplay-residfp/ */ // ------------------------------------ public int _databus; private int _cachedCycles; private bool _disableVoice3; private int _envelopeOutput0; private int _envelopeOutput1; private int _envelopeOutput2; private readonly Envelope[] _envelopes; private readonly Envelope _envelope0; private readonly Envelope _envelope1; private readonly Envelope _envelope2; private bool[] _filterEnable; private int _filterFrequency; private int _filterResonance; private bool _filterSelectBandPass; private bool _filterSelectLoPass; private bool _filterSelectHiPass; private int _mixer; private short[] _outputBuffer; private int[] _outputBuffer_filtered; private int[] _outputBuffer_not_filtered; private int[] _volume_at_sample_time; private int _outputBufferIndex; private int filter_index; private int last_filtered_value; private int _potCounter; private int _potX; private int _potY; private int _sample; private int _voiceOutput0; private int _voiceOutput1; private int _voiceOutput2; private readonly Voice[] _voices; private readonly Voice _voice0; private readonly Voice _voice1; private readonly Voice _voice2; private int _volume; public Func ReadPotX; public Func ReadPotY; public RealFFT fft; private readonly int _cpuCyclesNum; private int _sampleCyclesNum; private readonly int _sampleCyclesDen; private readonly int _sampleRate; public Sid(int[][] newWaveformTable, int sampleRate, int cyclesNum, int cyclesDen) { _sampleRate = sampleRate; _cpuCyclesNum = cyclesNum; _sampleCyclesDen = cyclesDen * sampleRate; _envelopes = new Envelope[3]; for (var i = 0; i < 3; i++) _envelopes[i] = new Envelope(); _envelope0 = _envelopes[0]; _envelope1 = _envelopes[1]; _envelope2 = _envelopes[2]; _voices = new Voice[3]; for (var i = 0; i < 3; i++) _voices[i] = new Voice(newWaveformTable); _voice0 = _voices[0]; _voice1 = _voices[1]; _voice2 = _voices[2]; _filterEnable = new bool[3]; for (var i = 0; i < 3; i++) _filterEnable[i] = false; _outputBuffer_filtered = new int[sampleRate]; _outputBuffer_not_filtered = new int[sampleRate]; _volume_at_sample_time = new int[sampleRate]; } // ------------------------------------ public void HardReset() { for (var i = 0; i < 3; i++) { _envelopes[i].HardReset(); _voices[i].HardReset(); } _potCounter = 0; _potX = 0; _potY = 0; } // ------------------------------------ public void ExecutePhase() { _cachedCycles++; // potentiometer values refresh every 512 cycles if (_potCounter == 0) { _potCounter = 512; _potX = ReadPotX(); _potY = ReadPotY(); } _potCounter--; } public void Flush(bool flushFilter) { while (_cachedCycles > 0) { _cachedCycles--; // process voices and envelopes _voice0.ExecutePhase2(); _voice1.ExecutePhase2(); _voice2.ExecutePhase2(); _envelope0.ExecutePhase2(); _envelope1.ExecutePhase2(); _envelope2.ExecutePhase2(); _voice0.Synchronize(_voice1, _voice2); _voice1.Synchronize(_voice2, _voice0); _voice2.Synchronize(_voice0, _voice1); // get output _voiceOutput0 = _voice0.Output(_voice2); _voiceOutput1 = _voice1.Output(_voice0); _voiceOutput2 = _voice2.Output(_voice1); _envelopeOutput0 = _envelope0.Level; _envelopeOutput1 = _envelope1.Level; _envelopeOutput2 = _envelope2.Level; int temp_v0 = (_voiceOutput0 * _envelopeOutput0); int temp_v1 = (_voiceOutput1 * _envelopeOutput1); int temp_v2 = (_voiceOutput2 * _envelopeOutput2); int temp_filtered = 0; int temp_not_filtered = 0; //note that voice 3 disable is relevent only if it is not going to the filter // see block diargam http://archive.6502.org/datasheets/mos_6581_sid.pdf if (!_filterEnable[2] && _disableVoice3) temp_v2 = 0; // break sound into filtered and non-filtered output // we need to process the filtered parts in bulk, so let's do it here if (_filterEnable[0]) temp_filtered += temp_v0; else temp_not_filtered += temp_v0; if (_filterEnable[1]) temp_filtered += temp_v1; else temp_not_filtered += temp_v1; if (_filterEnable[2]) temp_filtered += temp_v2; else temp_not_filtered += temp_v2; _sampleCyclesNum += _sampleCyclesDen; if (_sampleCyclesNum >= _cpuCyclesNum) { _sampleCyclesNum -= _cpuCyclesNum; if (_outputBufferIndex < _sampleRate) { _outputBuffer_not_filtered[_outputBufferIndex] = temp_not_filtered; _outputBuffer_filtered[_outputBufferIndex] = temp_filtered; _volume_at_sample_time[_outputBufferIndex] = _volume; _outputBufferIndex++; } } } //here we need to apply filtering to the samples and add them back to the buffer if (flushFilter) { if (_filterEnable[0] | _filterEnable[1] | _filterEnable[2]) { if ((_outputBufferIndex - filter_index) >= 16) { filter_operator(); } else { // the length is too short for the FFT to reliably act on the output // instead, clamp it to the previous output. for (int i = filter_index; i < _outputBufferIndex; i++) { _outputBuffer_filtered[i] = last_filtered_value; } } } filter_index = _outputBufferIndex; if (_outputBufferIndex>0) last_filtered_value = _outputBuffer_filtered[_outputBufferIndex - 1]; } // if the filter is off, keep updating the filter index to the most recent Flush if (!(_filterEnable[0] | _filterEnable[1] | _filterEnable[2])) { filter_index = _outputBufferIndex; } } public void filter_operator() { double loc_filterFrequency = (double)(_filterFrequency << 2) + 750; double attenuation; int nsamp = _outputBufferIndex - filter_index; // pass the list of filtered samples to the FFT // but needs to be a power of 2, so find the next highest power of 2 and re-sample int nsamp_2 = 2; bool test = true; while(test) { nsamp_2 *= 2; if (nsamp_2>nsamp) { test = false; } } fft = new RealFFT(nsamp_2); double[] temp_buffer = new double[nsamp_2]; // linearly interpolate the original sample set into the new denser sample set for (double i = 0; i < nsamp_2; i++) { temp_buffer[(int)i] = _outputBuffer_filtered[(int)Math.Floor((i / (nsamp_2-1) * (nsamp - 1))) + filter_index]; } // now we have everything we need to perform the FFT fft.ComputeForward(temp_buffer); // for each element in the frequency list, attenuate it according to the specs for (int i = 1; i < nsamp_2; i++) { double freq = i * ((double)(880*50)/nsamp); // add resonance effect // let's assume that frequencies near the peak are doubled in strength at max resonance if ((1.2 > freq / loc_filterFrequency) && (freq / loc_filterFrequency > 0.8 )) { temp_buffer[i] = temp_buffer[i] * (1 + (double)_filterResonance/15); } // low pass filter if (_filterSelectLoPass && freq > loc_filterFrequency) { //attenuated at 12db per octave attenuation = Math.Log(freq / loc_filterFrequency, 2); attenuation = 12 * attenuation; temp_buffer[i] = temp_buffer[i] * Math.Pow(2, -attenuation / 10); } // High pass filter if (_filterSelectHiPass && freq < loc_filterFrequency) { //attenuated at 12db per octave attenuation = Math.Log(loc_filterFrequency / freq, 2); attenuation = 12 * attenuation; temp_buffer[i] = temp_buffer[i] * Math.Pow(2, -attenuation / 10); } // Band pass filter if (_filterSelectBandPass) { //attenuated at 6db per octave attenuation = Math.Log(freq / loc_filterFrequency, 2); attenuation = 6 * attenuation; temp_buffer[i] = temp_buffer[i] * Math.Pow(2, -Math.Abs(attenuation) / 10); } } // now transform back into time space and reassemble the attenuated frequency components fft.ComputeReverse(temp_buffer); int temp = nsamp - 1; //re-sample back down to the original number of samples for (double i = 0; i < nsamp; i++) { _outputBuffer_filtered[(int)i + filter_index] = (int)(temp_buffer[(int)Math.Ceiling((i / (nsamp - 1) * (nsamp_2 - 1)))]/(nsamp_2/2)); if (_outputBuffer_filtered[(int)i + filter_index] < 0) { _outputBuffer_filtered[(int)i + filter_index] = 0; } // the FFT is only an approximate model and fails at low sample rates // what we want to do is limit how much the output samples can deviate from previous output // thus smoothing out the FT samples if (i<16) _outputBuffer_filtered[(int)i + filter_index] = (int)((last_filtered_value * Math.Pow(15 - i,1) + _outputBuffer_filtered[(int)i + filter_index] * Math.Pow(i,1))/ Math.Pow(15,1)); } } // ---------------------------------- public void SyncState(Serializer ser) { ser.Sync("_databus", ref _databus); ser.Sync("_cachedCycles", ref _cachedCycles); ser.Sync("_disableVoice3", ref _disableVoice3); ser.Sync("_envelopeOutput0", ref _envelopeOutput0); ser.Sync("_envelopeOutput1", ref _envelopeOutput1); ser.Sync("_envelopeOutput2", ref _envelopeOutput2); for (int i = 0; i < _envelopes.Length; i++) { ser.BeginSection("Envelope" + i); _envelopes[i].SyncState(ser); ser.EndSection(); } ser.Sync("_filterEnable", ref _filterEnable, useNull: false); ser.Sync("_filterFrequency", ref _filterFrequency); ser.Sync("_filterResonance", ref _filterResonance); ser.Sync("_filterSelectBandPass", ref _filterSelectBandPass); ser.Sync("_filterSelectLoPass", ref _filterSelectLoPass); ser.Sync("_filterSelectHiPass", ref _filterSelectHiPass); ser.Sync("_mixer", ref _mixer); ser.Sync("_potCounter", ref _potCounter); ser.Sync("_potX", ref _potX); ser.Sync("_potY", ref _potY); ser.Sync("_sample", ref _sample); ser.Sync("_voiceOutput0", ref _voiceOutput0); ser.Sync("_voiceOutput1", ref _voiceOutput1); ser.Sync("_voiceOutput2", ref _voiceOutput2); for (int i = 0; i < _voices.Length; i++) { ser.BeginSection("Voice" + i); _voices[i].SyncState(ser); ser.EndSection(); } ser.Sync("_volume", ref _volume); } } }