BizHawk/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/Sid.cs

386 lines
12 KiB
C#
Raw Normal View History

using System;
using BizHawk.Common;
namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
{
2016-02-22 23:50:11 +00:00
public sealed partial class Sid
{
2017-04-24 13:35:05 +00:00
/*
2016-02-22 23:50:11 +00:00
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/
*/
2017-04-24 13:35:05 +00:00
// ------------------------------------
2017-05-16 23:58:06 +00:00
public int _databus;
2017-04-24 13:35:05 +00:00
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;
2017-04-24 13:35:05 +00:00
private int _filterFrequency;
private int _filterResonance;
private bool _filterSelectBandPass;
private bool _filterSelectLoPass;
private bool _filterSelectHiPass;
private int _mixer;
2017-06-01 02:44:45 +00:00
private short[] _outputBuffer;
private readonly int[] _outputBufferFiltered;
private readonly int[] _outputBufferNotFiltered;
private readonly int[] _volumeAtSampleTime;
private int _outputBufferIndex;
private int _filterIndex;
private int _lastFilteredValue;
2017-04-24 13:35:05 +00:00
private int _potCounter;
private int _potX;
private int _potY;
private int _sample;
2017-04-24 13:35:05 +00:00
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;
2017-04-24 13:35:05 +00:00
private int _volume;
public Func<int> ReadPotX;
2016-02-22 23:50:11 +00:00
public Func<int> ReadPotY;
private RealFFT _fft;
private double[] _fftBuffer = new double[0];
private readonly int _cpuCyclesNum;
private int _sampleCyclesNum;
private readonly int _sampleCyclesDen;
private readonly int _sampleRate;
2016-02-22 23:50:11 +00:00
2017-04-24 13:35:05 +00:00
public Sid(int[][] newWaveformTable, int sampleRate, int cyclesNum, int cyclesDen)
{
_sampleRate = sampleRate;
_cpuCyclesNum = cyclesNum;
_sampleCyclesDen = cyclesDen * sampleRate;
2016-02-22 23:50:11 +00:00
2017-04-24 13:35:05 +00:00
_envelopes = new Envelope[3];
2016-02-22 23:50:11 +00:00
for (var i = 0; i < 3; i++)
_envelopes[i] = new Envelope();
2017-04-24 13:35:05 +00:00
_envelope0 = _envelopes[0];
_envelope1 = _envelopes[1];
_envelope2 = _envelopes[2];
2016-02-22 23:50:11 +00:00
2017-04-24 13:35:05 +00:00
_voices = new Voice[3];
2016-02-22 23:50:11 +00:00
for (var i = 0; i < 3; i++)
_voices[i] = new Voice(newWaveformTable);
2017-04-24 13:35:05 +00:00
_voice0 = _voices[0];
_voice1 = _voices[1];
_voice2 = _voices[2];
2016-02-22 23:50:11 +00:00
2017-04-24 13:35:05 +00:00
_filterEnable = new bool[3];
2016-02-22 23:50:11 +00:00
for (var i = 0; i < 3; i++)
_filterEnable[i] = false;
_outputBufferFiltered = new int[sampleRate];
_outputBufferNotFiltered = new int[sampleRate];
_volumeAtSampleTime = new int[sampleRate];
}
// ------------------------------------
public void HardReset()
{
2016-02-22 23:50:11 +00:00
for (var i = 0; i < 3; i++)
{
2016-02-22 23:50:11 +00:00
_envelopes[i].HardReset();
_voices[i].HardReset();
}
2016-02-22 23:50:11 +00:00
_potCounter = 0;
_potX = 0;
_potY = 0;
_filterEnable[0] = false;
_filterEnable[1] = false;
_filterEnable[2] = false;
_filterFrequency = 0;
_filterSelectBandPass = false;
_filterSelectHiPass = false;
_filterSelectLoPass = false;
_filterResonance = 0;
_volume = 0;
}
// ------------------------------------
2016-02-22 23:50:11 +00:00
public void ExecutePhase()
{
2016-02-22 23:50:11 +00:00
_cachedCycles++;
// potentiometer values refresh every 512 cycles
2016-02-22 23:50:11 +00:00
if (_potCounter == 0)
{
2016-02-22 23:50:11 +00:00
_potCounter = 512;
_potX = ReadPotX();
_potY = ReadPotY();
}
2017-05-30 17:09:46 +00:00
2016-02-22 23:50:11 +00:00
_potCounter--;
}
2017-05-30 17:09:46 +00:00
public void Flush(bool flushFilter)
{
2017-04-24 13:35:05 +00:00
while (_cachedCycles > 0)
{
_cachedCycles--;
2016-02-22 23:50:11 +00:00
// process voices and envelopes
_voice0.ExecutePhase2();
_voice1.ExecutePhase2();
_voice2.ExecutePhase2();
_envelope0.ExecutePhase2();
_envelope1.ExecutePhase2();
_envelope2.ExecutePhase2();
2017-04-24 13:35:05 +00:00
_voice0.Synchronize(_voice1, _voice2);
_voice1.Synchronize(_voice2, _voice0);
_voice2.Synchronize(_voice0, _voice1);
2016-02-22 23:50:11 +00:00
2017-04-24 13:35:05 +00:00
// get output
_voiceOutput0 = _voice0.Output(_voice2);
2016-02-22 23:50:11 +00:00
_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);
2017-05-29 01:28:03 +00:00
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;
2017-04-24 13:35:05 +00:00
_sampleCyclesNum += _sampleCyclesDen;
if (_sampleCyclesNum >= _cpuCyclesNum)
{
_sampleCyclesNum -= _cpuCyclesNum;
if (_outputBufferIndex < _sampleRate)
{
_outputBufferNotFiltered[_outputBufferIndex] = temp_not_filtered;
_outputBufferFiltered[_outputBufferIndex] = temp_filtered;
_volumeAtSampleTime[_outputBufferIndex] = _volume;
_outputBufferIndex++;
2017-04-24 13:35:05 +00:00
}
}
}
//here we need to apply filtering to the samples and add them back to the buffer
2017-05-30 17:09:46 +00:00
if (flushFilter)
2017-05-29 01:28:03 +00:00
{
if (_filterEnable[0] | _filterEnable[1] | _filterEnable[2])
{
if ((_outputBufferIndex - _filterIndex) >= 16)
{
2017-05-29 01:28:03 +00:00
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 = _filterIndex; i < _outputBufferIndex; i++)
2017-05-29 01:28:03 +00:00
{
_outputBufferFiltered[i] = _lastFilteredValue;
2017-05-29 01:28:03 +00:00
}
}
}
_filterIndex = _outputBufferIndex;
2017-06-01 02:44:45 +00:00
if (_outputBufferIndex>0)
_lastFilteredValue = _outputBufferFiltered[_outputBufferIndex - 1];
}
2017-05-29 01:28:03 +00:00
// if the filter is off, keep updating the filter index to the most recent Flush
if (!(_filterEnable[0] | _filterEnable[1] | _filterEnable[2]))
{
_filterIndex = _outputBufferIndex;
2017-06-01 02:44:45 +00:00
}
2017-04-24 13:35:05 +00:00
}
2017-05-29 01:28:03 +00:00
public void filter_operator()
{
2017-06-01 02:44:45 +00:00
double loc_filterFrequency = (double)(_filterFrequency << 2) + 750;
double attenuation;
int nsamp = _outputBufferIndex - _filterIndex;
// 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);
// eventually this will settle on a single buffer size and stop reallocating
if (_fftBuffer.Length < nsamp_2)
Array.Resize(ref _fftBuffer, nsamp_2);
// linearly interpolate the original sample set into the new denser sample set
for (double i = 0; i < nsamp_2; i++)
{
_fftBuffer[(int)i] = _outputBufferFiltered[(int)Math.Floor((i / (nsamp_2-1) * (nsamp - 1))) + _filterIndex];
}
2017-05-29 01:28:03 +00:00
// now we have everything we need to perform the FFT
_fft.ComputeForward(_fftBuffer);
2017-06-01 02:44:45 +00:00
// for each element in the frequency list, attenuate it according to the specs
2017-06-01 02:44:45 +00:00
for (int i = 1; i < nsamp_2; i++)
{
2017-06-01 02:44:45 +00:00
double freq = i * ((double)(880*50)/nsamp);
2017-05-29 01:28:03 +00:00
// 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 ))
{
_fftBuffer[i] = _fftBuffer[i] * (1 + (double)_filterResonance/15);
2017-05-29 01:28:03 +00:00
}
// low pass filter
if (_filterSelectLoPass && freq > loc_filterFrequency)
{
//attenuated at 12db per octave
attenuation = Math.Log(freq / loc_filterFrequency, 2);
attenuation = 12 * attenuation;
_fftBuffer[i] = _fftBuffer[i] * Math.Pow(2, -attenuation / 10);
}
// High pass filter
2017-06-01 02:44:45 +00:00
if (_filterSelectHiPass && freq < loc_filterFrequency)
{
//attenuated at 12db per octave
2017-06-01 02:44:45 +00:00
attenuation = Math.Log(loc_filterFrequency / freq, 2);
attenuation = 12 * attenuation;
_fftBuffer[i] = _fftBuffer[i] * Math.Pow(2, -attenuation / 10);
}
// Band pass filter
if (_filterSelectBandPass)
{
//attenuated at 6db per octave
2017-06-01 02:44:45 +00:00
attenuation = Math.Log(freq / loc_filterFrequency, 2);
attenuation = 6 * attenuation;
_fftBuffer[i] = _fftBuffer[i] * Math.Pow(2, -Math.Abs(attenuation) / 10);
}
}
2017-06-01 02:44:45 +00:00
// now transform back into time space and reassemble the attenuated frequency components
_fft.ComputeReverse(_fftBuffer);
2017-05-29 01:28:03 +00:00
int temp = nsamp - 1;
//re-sample back down to the original number of samples
2017-05-29 01:28:03 +00:00
for (double i = 0; i < nsamp; i++)
{
_outputBufferFiltered[(int)i + _filterIndex] = (int)(_fftBuffer[(int)Math.Ceiling((i / (nsamp - 1) * (nsamp_2 - 1)))]/(nsamp_2/2));
2017-05-29 01:28:03 +00:00
if (_outputBufferFiltered[(int)i + _filterIndex] < 0)
{
_outputBufferFiltered[(int)i + _filterIndex] = 0;
}
2017-05-29 01:28:03 +00:00
// 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)
_outputBufferFiltered[(int)i + _filterIndex] = (int)((_lastFilteredValue * Math.Pow(15 - i,1) + _outputBufferFiltered[(int)i + _filterIndex] * Math.Pow(i,1))/ Math.Pow(15,1));
}
}
2017-04-24 13:35:05 +00:00
// ----------------------------------
public void SyncState(Serializer ser)
{
2019-03-28 03:18:58 +00:00
ser.Sync(nameof(_databus), ref _databus);
ser.Sync(nameof(_cachedCycles), ref _cachedCycles);
ser.Sync(nameof(_disableVoice3), ref _disableVoice3);
ser.Sync(nameof(_envelopeOutput0), ref _envelopeOutput0);
ser.Sync(nameof(_envelopeOutput1), ref _envelopeOutput1);
ser.Sync(nameof(_envelopeOutput2), ref _envelopeOutput2);
for (int i = 0; i < _envelopes.Length; i++)
{
ser.BeginSection("Envelope" + i);
_envelopes[i].SyncState(ser);
ser.EndSection();
}
2019-03-28 03:18:58 +00:00
ser.Sync(nameof(_filterEnable), ref _filterEnable, useNull: false);
ser.Sync(nameof(_filterFrequency), ref _filterFrequency);
ser.Sync(nameof(_filterResonance), ref _filterResonance);
ser.Sync(nameof(_filterSelectBandPass), ref _filterSelectBandPass);
ser.Sync(nameof(_filterSelectLoPass), ref _filterSelectLoPass);
ser.Sync(nameof(_filterSelectHiPass), ref _filterSelectHiPass);
ser.Sync(nameof(_mixer), ref _mixer);
ser.Sync(nameof(_potCounter), ref _potCounter);
ser.Sync(nameof(_potX), ref _potX);
ser.Sync(nameof(_potY), ref _potY);
ser.Sync(nameof(_sample), ref _sample);
ser.Sync(nameof(_voiceOutput0), ref _voiceOutput0);
ser.Sync(nameof(_voiceOutput1), ref _voiceOutput1);
ser.Sync(nameof(_voiceOutput2), ref _voiceOutput2);
for (int i = 0; i < _voices.Length; i++)
{
ser.BeginSection("Voice" + i);
_voices[i].SyncState(ser);
ser.EndSection();
}
2019-03-28 03:18:58 +00:00
ser.Sync(nameof(_volume), ref _volume);
}
}
}