diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.cs
index 204d406d7f..0eb6e977cc 100644
--- a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.cs
+++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumAudioSettings.cs
@@ -22,7 +22,7 @@ namespace BizHawk.Client.EmuHawk
_settings = ((ZXSpectrum)Global.Emulator).GetSettings().Clone();
// AY panning config
- var panTypes = Enum.GetNames(typeof(AYChip.AYPanConfig));
+ var panTypes = Enum.GetNames(typeof(AY38912.AYPanConfig));
foreach (var val in panTypes)
{
panTypecomboBox1.Items.Add(val);
@@ -51,7 +51,7 @@ namespace BizHawk.Client.EmuHawk
if (changed)
{
- _settings.AYPanConfig = (AYChip.AYPanConfig)Enum.Parse(typeof(AYChip.AYPanConfig), panTypecomboBox1.SelectedItem.ToString());
+ _settings.AYPanConfig = (AY38912.AYPanConfig)Enum.Parse(typeof(AY38912.AYPanConfig), panTypecomboBox1.SelectedItem.ToString());
_settings.TapeVolume = tapeVolumetrackBar.Value;
_settings.EarVolume = earVolumetrackBar.Value;
diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
index 980cdafec2..75b1906e1a 100644
--- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
+++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
@@ -259,6 +259,8 @@
+
+
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs
new file mode 100644
index 0000000000..dc5f101adf
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs
@@ -0,0 +1,811 @@
+using BizHawk.Common;
+using BizHawk.Emulation.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
+{
+ ///
+ /// AY-3-8912 Emulated Device
+ ///
+ /// Based heavily on the YM-2149F / AY-3-8910 emulator used in Unreal Speccy
+ /// (Originally created under Public Domain license by SMT jan.2006)
+ ///
+ /// https://github.com/mkoloberdin/unrealspeccy/blob/master/sndrender/sndchip.cpp
+ /// https://github.com/mkoloberdin/unrealspeccy/blob/master/sndrender/sndchip.h
+ ///
+ public class AY38912 : IPSG
+ {
+ #region Device Fields
+
+ ///
+ /// The emulated machine (passed in via constructor)
+ ///
+ private SpectrumBase _machine;
+
+ private int _tStatesPerFrame;
+ private int _sampleRate;
+ private int _samplesPerFrame;
+ private int _tStatesPerSample;
+ private short[] _audioBuffer;
+ private int _audioBufferIndex;
+ private int _lastStateRendered;
+
+ #endregion
+
+ #region Construction & Initialization
+
+ ///
+ /// Main constructor
+ ///
+ public AY38912(SpectrumBase machine)
+ {
+ _machine = machine;
+ }
+
+ ///
+ /// Initialises the AY chip
+ ///
+ public void Init(int sampleRate, int tStatesPerFrame)
+ {
+ InitTiming(sampleRate, tStatesPerFrame);
+ UpdateVolume();
+ Reset();
+ }
+
+ #endregion
+
+ #region IPortIODevice
+
+ public bool ReadPort(ushort port, ref int value)
+ {
+ if (port != 0xfffd)
+ {
+ // port read is not addressing this device
+ return false;
+ }
+
+ value = PortRead();
+
+ return true;
+ }
+
+ public bool WritePort(ushort port, int value)
+ {
+ if (port == 0xfffd)
+ {
+ // register select
+ SelectedRegister = value & 0x0f;
+ return true;
+ }
+ else if (port == 0xbffd)
+ {
+ // Update the audiobuffer based on the current CPU cycle
+ // (this process the previous data BEFORE writing to the currently selected register)
+ int d = (int)(_machine.CurrentFrameCycle);
+ BufferUpdate(d);
+
+ // write to register
+ PortWrite(value);
+ return true;
+ }
+ return false;
+ }
+
+ #endregion
+
+ #region AY Implementation
+
+ #region Public Properties
+
+ ///
+ /// AY mixer panning configuration
+ ///
+ [Flags]
+ public enum AYPanConfig
+ {
+ MONO = 0,
+ ABC = 1,
+ ACB = 2,
+ BAC = 3,
+ BCA = 4,
+ CAB = 5,
+ CBA = 6,
+ }
+
+ ///
+ /// The AY panning configuration
+ ///
+ public AYPanConfig PanningConfiguration
+ {
+ get
+ {
+ return _currentPanTab;
+ }
+ set
+ {
+ if (value != _currentPanTab)
+ {
+ _currentPanTab = value;
+ UpdateVolume();
+ }
+ }
+ }
+
+ ///
+ /// The AY chip output volume
+ /// (0 - 100)
+ ///
+ public int Volume
+ {
+ get
+ {
+ return _volume;
+ }
+ set
+ {
+ //value = Math.Max(0, value);
+ //value = Math.Max(100, value);
+ if (_volume == value)
+ {
+ return;
+ }
+ _volume = value;
+ UpdateVolume();
+ }
+ }
+
+ ///
+ /// The currently selected register
+ ///
+ public int SelectedRegister
+ {
+ get { return _activeRegister; }
+ set
+ {
+ _activeRegister = (byte)value;
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Resets the PSG
+ ///
+ public void Reset()
+ {
+ /*
+ _noiseVal = 0x0FFFF;
+ _outABC = 0;
+ _outNoiseABC = 0;
+ _counterNoise = 0;
+ _counterA = 0;
+ _counterB = 0;
+ _counterC = 0;
+ _EnvelopeCounterBend = 0;
+
+ // clear all the registers
+ for (int i = 0; i < 14; i++)
+ {
+ SelectedRegister = i;
+ PortWrite(0);
+ }
+
+ randomSeed = 1;
+
+ // number of frames to update
+ var fr = (_audioBufferIndex * _tStatesPerFrame) / _audioBuffer.Length;
+
+ // update the audio buffer
+ BufferUpdate(fr);
+ */
+ }
+
+ ///
+ /// Reads the value from the currently selected register
+ ///
+ ///
+ public int PortRead()
+ {
+ return _registers[_activeRegister];
+ }
+
+ ///
+ /// Writes to the currently selected register
+ ///
+ ///
+ public void PortWrite(int value)
+ {
+ if (_activeRegister >= 0x10)
+ return;
+
+ byte val = (byte)value;
+
+ if (((1 << _activeRegister) & ((1 << 1) | (1 << 3) | (1 << 5) | (1 << 13))) != 0)
+ val &= 0x0F;
+
+ if (((1 << _activeRegister) & ((1 << 6) | (1 << 8) | (1 << 9) | (1 << 10))) != 0)
+ val &= 0x1F;
+
+ if (_activeRegister != 13 && _registers[_activeRegister] == val)
+ return;
+
+ _registers[_activeRegister] = val;
+
+ switch (_activeRegister)
+ {
+ // Channel A (Combined Pitch)
+ // (not written to directly)
+ case 0:
+ case 1:
+ _dividerA = _registers[AY_A_FINE] | (_registers[AY_A_COARSE] << 8);
+ break;
+ // Channel B (Combined Pitch)
+ // (not written to directly)
+ case 2:
+ case 3:
+ _dividerB = _registers[AY_B_FINE] | (_registers[AY_B_COARSE] << 8);
+ break;
+ // Channel C (Combined Pitch)
+ // (not written to directly)
+ case 4:
+ case 5:
+ _dividerC = _registers[AY_C_FINE] | (_registers[AY_C_COARSE] << 8);
+ break;
+ // Noise Pitch
+ case 6:
+ _dividerN = val * 2;
+ break;
+ // Mixer
+ case 7:
+ _bit0 = 0 - ((val >> 0) & 1);
+ _bit1 = 0 - ((val >> 1) & 1);
+ _bit2 = 0 - ((val >> 2) & 1);
+ _bit3 = 0 - ((val >> 3) & 1);
+ _bit4 = 0 - ((val >> 4) & 1);
+ _bit5 = 0 - ((val >> 5) & 1);
+ break;
+ // Channel Volumes
+ case 8:
+ _eMaskA = (val & 0x10) != 0 ? -1 : 0;
+ _vA = ((val & 0x0F) * 2 + 1) & ~_eMaskA;
+ break;
+ case 9:
+ _eMaskB = (val & 0x10) != 0 ? -1 : 0;
+ _vB = ((val & 0x0F) * 2 + 1) & ~_eMaskB;
+ break;
+ case 10:
+ _eMaskC = (val & 0x10) != 0 ? -1 : 0;
+ _vC = ((val & 0x0F) * 2 + 1) & ~_eMaskC;
+ break;
+ // Envelope (Combined Duration)
+ // (not written to directly)
+ case 11:
+ case 12:
+ _dividerE = _registers[AY_E_FINE] | (_registers[AY_E_COARSE] << 8);
+ break;
+ // Envelope Shape
+ case 13:
+ // reset the envelope counter
+ _countE = 0;
+
+ if ((_registers[AY_E_SHAPE] & 4) != 0)
+ {
+ // attack
+ _eState = 0;
+ _eDirection = 1;
+ }
+ else
+ {
+ // decay
+ _eState = 31;
+ _eDirection = -1;
+ }
+ break;
+ case 14:
+ // IO Port - not implemented
+ break;
+ }
+ }
+
+ ///
+ /// Start of frame
+ ///
+ public void StartFrame()
+ {
+ _audioBufferIndex = 0;
+ BufferUpdate(0);
+ }
+
+ ///
+ /// End of frame
+ ///
+ public void EndFrame()
+ {
+ BufferUpdate(_tStatesPerFrame);
+ }
+
+ ///
+ /// Updates the audiobuffer based on the current frame t-state
+ ///
+ ///
+ public void UpdateSound(int frameCycle)
+ {
+ BufferUpdate(frameCycle);
+ }
+
+ #endregion
+
+ #region Private Fields
+
+ ///
+ /// Register indicies
+ ///
+ private const int AY_A_FINE = 0;
+ private const int AY_A_COARSE = 1;
+ private const int AY_B_FINE = 2;
+ private const int AY_B_COARSE = 3;
+ private const int AY_C_FINE = 4;
+ private const int AY_C_COARSE = 5;
+ private const int AY_NOISEPITCH = 6;
+ private const int AY_MIXER = 7;
+ private const int AY_A_VOL = 8;
+ private const int AY_B_VOL = 9;
+ private const int AY_C_VOL = 10;
+ private const int AY_E_FINE = 11;
+ private const int AY_E_COARSE = 12;
+ private const int AY_E_SHAPE = 13;
+ private const int AY_PORT_A = 14;
+ private const int AY_PORT_B = 15;
+
+ ///
+ /// The register array
+ /*
+ The AY-3-8910/8912 contains 16 internal registers as follows:
+
+ Register Function Range
+ 0 Channel A fine pitch 8-bit (0-255)
+ 1 Channel A course pitch 4-bit (0-15)
+ 2 Channel B fine pitch 8-bit (0-255)
+ 3 Channel B course pitch 4-bit (0-15)
+ 4 Channel C fine pitch 8-bit (0-255)
+ 5 Channel C course pitch 4-bit (0-15)
+ 6 Noise pitch 5-bit (0-31)
+ 7 Mixer 8-bit (see below)
+ 8 Channel A volume 4-bit (0-15, see below)
+ 9 Channel B volume 4-bit (0-15, see below)
+ 10 Channel C volume 4-bit (0-15, see below)
+ 11 Envelope fine duration 8-bit (0-255)
+ 12 Envelope course duration 8-bit (0-255)
+ 13 Envelope shape 4-bit (0-15)
+ 14 I/O port A 8-bit (0-255)
+ 15 I/O port B 8-bit (0-255) (Not present on the AY-3-8912)
+
+ * The volume registers (8, 9 and 10) contain a 4-bit setting but if bit 5 is set then that channel uses the
+ envelope defined by register 13 and ignores its volume setting.
+ * The mixer (register 7) is made up of the following bits (low=enabled):
+
+ Bit: 7 6 5 4 3 2 1 0
+ Register: I/O I/O Noise Noise Noise Tone Tone Tone
+ Channel: B A C B A C B A
+
+ The AY-3-8912 ignores bit 7 of this register.
+ */
+ ///
+ private int[] _registers = new int[16];
+
+ ///
+ /// The currently selected register
+ ///
+ private byte _activeRegister;
+
+ ///
+ /// The frequency of the AY chip
+ ///
+ private static int _chipFrequency = 1773400;
+
+ ///
+ /// The rendering resolution of the chip
+ ///
+ private double _resolution = 50D * 8D / _chipFrequency;
+
+ ///
+ /// Channel generator state
+ ///
+ private int _bitA;
+ private int _bitB;
+ private int _bitC;
+
+ ///
+ /// Envelope state
+ ///
+ private int _eState;
+
+ ///
+ /// Envelope direction
+ ///
+ private int _eDirection;
+
+ ///
+ /// Noise seed
+ ///
+ private int _noiseSeed;
+
+ ///
+ /// Mixer state
+ ///
+ private int _bit0;
+ private int _bit1;
+ private int _bit2;
+ private int _bit3;
+ private int _bit4;
+ private int _bit5;
+
+ ///
+ /// Noise generator state
+ ///
+ private int _bitN;
+
+ ///
+ /// Envelope masks
+ ///
+ private int _eMaskA;
+ private int _eMaskB;
+ private int _eMaskC;
+
+ ///
+ /// Amplitudes
+ ///
+ private int _vA;
+ private int _vB;
+ private int _vC;
+
+ ///
+ /// Channel gen counters
+ ///
+ private int _countA;
+ private int _countB;
+ private int _countC;
+
+ ///
+ /// Envelope gen counter
+ ///
+ private int _countE;
+
+ ///
+ /// Noise gen counter
+ ///
+ private int _countN;
+
+ ///
+ /// Channel gen dividers
+ ///
+ private int _dividerA;
+ private int _dividerB;
+ private int _dividerC;
+
+ ///
+ /// Envelope gen divider
+ ///
+ private int _dividerE;
+
+ ///
+ /// Noise gen divider
+ ///
+ private int _dividerN;
+
+ ///
+ /// Panning table list
+ ///
+ private static List PanTabs = new List
+ {
+ // MONO
+ new uint[] { 50,50, 50,50, 50,50 },
+ // ABC
+ new uint[] { 100,10, 66,66, 10,100 },
+ // ACB
+ new uint[] { 100,10, 10,100, 66,66 },
+ // BAC
+ new uint[] { 66,66, 100,10, 10,100 },
+ // BCA
+ new uint[] { 10,100, 100,10, 66,66 },
+ // CAB
+ new uint[] { 66,66, 10,100, 100,10 },
+ // CBA
+ new uint[] { 10,100, 66,66, 100,10 }
+ };
+
+ ///
+ /// The currently selected panning configuration
+ ///
+ private AYPanConfig _currentPanTab = AYPanConfig.ABC;
+
+ ///
+ /// The current volume
+ ///
+ private int _volume = 75;
+
+ ///
+ /// Volume tables state
+ ///
+ private uint[][] _volumeTables;
+
+ ///
+ /// Volume table to be used
+ ///
+ private static uint[] AYVolumes = new uint[]
+ {
+ 0x0000,0x0000,0x0340,0x0340,0x04C0,0x04C0,0x06F2,0x06F2,
+ 0x0A44,0x0A44,0x0F13,0x0F13,0x1510,0x1510,0x227E,0x227E,
+ 0x289F,0x289F,0x414E,0x414E,0x5B21,0x5B21,0x7258,0x7258,
+ 0x905E,0x905E,0xB550,0xB550,0xD7A0,0xD7A0,0xFFFF,0xFFFF,
+ };
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Forces an update of the volume tables
+ ///
+ private void UpdateVolume()
+ {
+ int upperFloor = 40000;
+ var inc = (0xFFFF - upperFloor) / 100;
+
+ var vol = inc * _volume; // ((ulong)0xFFFF * (ulong)_volume / 100UL) - 20000 ;
+ _volumeTables = new uint[6][];
+
+ // parent array
+ for (int j = 0; j < _volumeTables.Length; j++)
+ {
+ _volumeTables[j] = new uint[32];
+
+ // child array
+ for (int i = 0; i < _volumeTables[j].Length; i++)
+ {
+ _volumeTables[j][i] = (uint)(
+ (PanTabs[(int)_currentPanTab][j] * AYVolumes[i] * vol) /
+ (3 * 65535 * 100));
+ }
+ }
+ }
+
+ private int mult_const;
+
+ ///
+ /// Initializes timing information for the frame
+ ///
+ ///
+ ///
+ private void InitTiming(int sampleRate, int frameTactCount)
+ {
+ _sampleRate = sampleRate;
+ _tStatesPerFrame = frameTactCount;
+ _samplesPerFrame = 882;
+
+ _tStatesPerSample = 79; //(int)Math.Round(((double)_tStatesPerFrame * 50D) /
+ //(16D * (double)_sampleRate),
+ //MidpointRounding.AwayFromZero);
+
+ //_samplesPerFrame = _tStatesPerFrame / _tStatesPerSample;
+ _audioBuffer = new short[_samplesPerFrame * 2]; //[_sampleRate / 50];
+ _audioBufferIndex = 0;
+
+ mult_const = ((_chipFrequency / 8) << 14) / _machine.ULADevice.ClockSpeed;
+
+ var aytickspercputick = (double)_machine.ULADevice.ClockSpeed / (double)_chipFrequency;
+ int ayCyclesPerSample = (int)((double)_tStatesPerSample * (double)aytickspercputick);
+ }
+
+ ///
+ /// Updates the audiobuffer based on the current frame t-state
+ ///
+ ///
+ private void BufferUpdate(int cycle)
+ {
+ if (cycle > _tStatesPerFrame)
+ {
+ // we are outside of the frame - just process the last value
+ cycle = _tStatesPerFrame;
+ }
+
+ // get the current length of the audiobuffer
+ int bufferLength = _samplesPerFrame; // _audioBuffer.Length;
+
+ int toEnd = ((bufferLength * cycle) / _tStatesPerFrame);
+
+ // loop through the number of samples we need to render
+ while (_audioBufferIndex < toEnd)
+ {
+ // run the AY chip processing at the correct resolution
+ for (int i = 0; i < _tStatesPerSample / 14; i++)
+ {
+ if (++_countA >= _dividerA)
+ {
+ _countA = 0;
+ _bitA ^= -1;
+ }
+
+ if (++_countB >= _dividerB)
+ {
+ _countB = 0;
+ _bitB ^= -1;
+ }
+
+ if (++_countC >= _dividerC)
+ {
+ _countC = 0;
+ _bitC ^= -1;
+ }
+
+ if (++_countN >= _dividerN)
+ {
+ _countN = 0;
+ _noiseSeed = (_noiseSeed * 2 + 1) ^ (((_noiseSeed >> 16) ^ (_noiseSeed >> 13)) & 1);
+ _bitN = 0 - ((_noiseSeed >> 16) & 1);
+ }
+
+ if (++_countE >= _dividerE)
+ {
+ _countE = 0;
+ _eState += +_eDirection;
+
+ if ((_eState & ~31) != 0)
+ {
+ var mask = (1 << _registers[AY_E_SHAPE]);
+
+ if ((mask & ((1 << 0) | (1 << 1) | (1 << 2) |
+ (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) |
+ (1 << 7) | (1 << 9) | (1 << 15))) != 0)
+ {
+ _eState = _eDirection = 0;
+ }
+ else if ((mask & ((1 << 8) | (1 << 12))) != 0)
+ {
+ _eState &= 31;
+ }
+ else if ((mask & ((1 << 10) | (1 << 14))) != 0)
+ {
+ _eDirection = -_eDirection;
+ _eState += _eDirection;
+ }
+ else
+ {
+ // 11,13
+ _eState = 31;
+ _eDirection = 0;
+ }
+ }
+ }
+ }
+
+ // mix the sample
+ var mixA = ((_eMaskA & _eState) | _vA) & ((_bitA | _bit0) & (_bitN | _bit3));
+ var mixB = ((_eMaskB & _eState) | _vB) & ((_bitB | _bit1) & (_bitN | _bit4));
+ var mixC = ((_eMaskC & _eState) | _vC) & ((_bitC | _bit2) & (_bitN | _bit5));
+
+ var l = _volumeTables[0][mixA];
+ var r = _volumeTables[1][mixA];
+
+ l += _volumeTables[2][mixB];
+ r += _volumeTables[3][mixB];
+ l += _volumeTables[4][mixC];
+ r += _volumeTables[5][mixC];
+
+ _audioBuffer[_audioBufferIndex * 2] = (short)l;
+ _audioBuffer[(_audioBufferIndex * 2) + 1] = (short)r;
+
+ _audioBufferIndex++;
+ }
+
+ _lastStateRendered = cycle;
+ }
+
+ #endregion
+
+ #endregion
+
+ #region ISoundProvider
+
+ public bool CanProvideAsync => false;
+
+ public SyncSoundMode SyncMode => SyncSoundMode.Sync;
+
+ public void SetSyncMode(SyncSoundMode mode)
+ {
+ if (mode != SyncSoundMode.Sync)
+ throw new InvalidOperationException("Only Sync mode is supported.");
+ }
+
+ public void GetSamplesAsync(short[] samples)
+ {
+ throw new NotSupportedException("Async is not available");
+ }
+
+ public void DiscardSamples()
+ {
+ _audioBuffer = new short[_samplesPerFrame * 2];
+ }
+
+ public void GetSamplesSync(out short[] samples, out int nsamp)
+ {
+ nsamp = _samplesPerFrame;
+ samples = _audioBuffer;
+ DiscardSamples();
+ }
+
+ #endregion
+
+ #region State Serialization
+
+ public int nullDump = 0;
+
+ ///
+ /// State serialization
+ ///
+ ///
+ public void SyncState(Serializer ser)
+ {
+ ser.BeginSection("PSG-AY");
+
+ ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame);
+ ser.Sync("_sampleRate", ref _sampleRate);
+ ser.Sync("_samplesPerFrame", ref _samplesPerFrame);
+ ser.Sync("_tStatesPerSample", ref _tStatesPerSample);
+ ser.Sync("_audioBufferIndex", ref _audioBufferIndex);
+ ser.Sync("_audioBuffer", ref _audioBuffer, false);
+
+ ser.Sync("_registers", ref _registers, false);
+ ser.Sync("_activeRegister", ref _activeRegister);
+ ser.Sync("_bitA", ref _bitA);
+ ser.Sync("_bitB", ref _bitB);
+ ser.Sync("_bitC", ref _bitC);
+ ser.Sync("_eState", ref _eState);
+ ser.Sync("_eDirection", ref _eDirection);
+ ser.Sync("_noiseSeed", ref _noiseSeed);
+ ser.Sync("_bit0", ref _bit0);
+ ser.Sync("_bit1", ref _bit1);
+ ser.Sync("_bit2", ref _bit2);
+ ser.Sync("_bit3", ref _bit3);
+ ser.Sync("_bit4", ref _bit4);
+ ser.Sync("_bit5", ref _bit5);
+ ser.Sync("_bitN", ref _bitN);
+ ser.Sync("_eMaskA", ref _eMaskA);
+ ser.Sync("_eMaskB", ref _eMaskB);
+ ser.Sync("_eMaskC", ref _eMaskC);
+ ser.Sync("_vA", ref _vA);
+ ser.Sync("_vB", ref _vB);
+ ser.Sync("_vC", ref _vC);
+ ser.Sync("_countA", ref _countA);
+ ser.Sync("_countB", ref _countB);
+ ser.Sync("_countC", ref _countC);
+ ser.Sync("_countE", ref _countE);
+ ser.Sync("_countN", ref _countN);
+ ser.Sync("_dividerA", ref _dividerA);
+ ser.Sync("_dividerB", ref _dividerB);
+ ser.Sync("_dividerC", ref _dividerC);
+ ser.Sync("_dividerE", ref _dividerE);
+ ser.Sync("_dividerN", ref _dividerN);
+ ser.SyncEnum("_currentPanTab", ref _currentPanTab);
+ ser.Sync("_volume", ref nullDump);
+
+ for (int i = 0; i < 6; i++)
+ {
+ ser.Sync("volTable" + i, ref _volumeTables[i], false);
+ }
+
+ if (ser.IsReader)
+ _volume = _machine.Spectrum.Settings.AYVolume;
+
+ ser.EndSection();
+ }
+
+ #endregion
+ }
+}
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs
index 161b93176b..cac7b80229 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
+ /*
///
/// AY-3-8912 Emulated Device
///
@@ -177,31 +178,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
///
public void Reset()
{
- /*
- _noiseVal = 0x0FFFF;
- _outABC = 0;
- _outNoiseABC = 0;
- _counterNoise = 0;
- _counterA = 0;
- _counterB = 0;
- _counterC = 0;
- _EnvelopeCounterBend = 0;
-
- // clear all the registers
- for (int i = 0; i < 14; i++)
- {
- SelectedRegister = i;
- PortWrite(0);
- }
-
- randomSeed = 1;
-
- // number of frames to update
- var fr = (_audioBufferIndex * _tStatesPerFrame) / _audioBuffer.Length;
-
- // update the audio buffer
- BufferUpdate(fr);
- */
+
}
///
@@ -394,6 +371,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
The AY-3-8912 ignores bit 7 of this register.
*/
+ /*
///
private int[] _registers = new int[16];
@@ -585,12 +563,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
_sampleRate = sampleRate;
_tStatesPerFrame = frameTactCount;
+ _samplesPerFrame = 882;
_tStatesPerSample = 79; //(int)Math.Round(((double)_tStatesPerFrame * 50D) /
//(16D * (double)_sampleRate),
//MidpointRounding.AwayFromZero);
- _samplesPerFrame = _tStatesPerFrame / _tStatesPerSample;
+ //_samplesPerFrame = _tStatesPerFrame / _tStatesPerSample;
_audioBuffer = new short[_samplesPerFrame * 2]; //[_sampleRate / 50];
_audioBufferIndex = 0;
@@ -805,4 +784,5 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
#endregion
}
+ */
}
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Beeper.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Beeper.cs
new file mode 100644
index 0000000000..6607e79e1e
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Beeper.cs
@@ -0,0 +1,232 @@
+using BizHawk.Common;
+using BizHawk.Emulation.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
+{
+ ///
+ /// Logical Beeper class
+ /// Represents the piezoelectric buzzer used in the Spectrum to produce sound
+ /// The beeper is controlled by rapidly toggling bit 4 of port &FE
+ ///
+ /// It is instantiated twice, once for speccy beeper output, and once tape buzzer emulation
+ ///
+ /// This implementation uses BlipBuffer and should *always* output at 44100 with 882 samples per frame
+ /// (so that it can be mixed easily further down the line)
+ ///
+ public class Beeper : ISoundProvider, IBeeperDevice
+ {
+ #region Fields and Properties
+
+ ///
+ /// Sample Rate
+ /// This usually has to be 44100 for ISoundProvider
+ ///
+ private int _sampleRate;
+ public int SampleRate
+ {
+ get { return _sampleRate; }
+ set { _sampleRate = value; }
+ }
+
+ ///
+ /// Buzzer volume
+ /// Accepts an int 0-100 value
+ ///
+ private int _volume;
+ public int Volume
+ {
+ get
+ {
+ return VolumeConverterOut(_volume);
+ }
+ set
+ {
+ var newVol = VolumeConverterIn(value);
+ if (newVol != _volume)
+ blip.Clear();
+ _volume = VolumeConverterIn(value);
+ }
+ }
+
+ ///
+ /// The last used volume (used to modify blipbuffer delta values)
+ ///
+ private int lastVolume;
+
+ ///
+ /// The number of cpu cycles per frame
+ ///
+ private long _tStatesPerFrame;
+
+ ///
+ /// The parent emulated machine
+ ///
+ private SpectrumBase _machine;
+
+ ///
+ /// The last pulse
+ ///
+ private bool LastPulse;
+
+ ///
+ /// The last T-State (cpu cycle) that the last pulse was received
+ ///
+ private long LastPulseTState;
+
+ ///
+ /// Device blipbuffer
+ ///
+ private readonly BlipBuffer blip = new BlipBuffer(882);
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Takes an int 0-100 and returns the relevant short volume to output
+ ///
+ ///
+ ///
+ private int VolumeConverterIn(int vol)
+ {
+ int maxLimit = short.MaxValue / 3;
+ int increment = maxLimit / 100;
+
+ return vol * increment;
+ }
+
+ ///
+ /// Takes an short volume and returns the relevant int value 0-100
+ ///
+ ///
+ ///
+ private int VolumeConverterOut(int shortvol)
+ {
+ int maxLimit = short.MaxValue / 3;
+ int increment = maxLimit / 100;
+
+ if (shortvol > maxLimit)
+ shortvol = maxLimit;
+
+ return shortvol / increment;
+ }
+
+ #endregion
+
+ #region Construction & Initialisation
+
+ public Beeper(SpectrumBase machine)
+ {
+ _machine = machine;
+ }
+
+ ///
+ /// Initialises the beeper
+ ///
+ public void Init(int sampleRate, int tStatesPerFrame)
+ {
+ blip.SetRates((tStatesPerFrame * 50), sampleRate);
+ _sampleRate = sampleRate;
+ _tStatesPerFrame = tStatesPerFrame;
+ }
+
+ #endregion
+
+ #region IBeeperDevice
+
+ ///
+ /// Processes an incoming pulse value and adds it to the blipbuffer
+ ///
+ ///
+ public void ProcessPulseValue(bool pulse)
+ {
+ if (!_machine._renderSound)
+ return;
+
+ if (LastPulse == pulse)
+ {
+ // no change
+ blip.AddDelta((uint)_machine.CurrentFrameCycle, 0);
+ }
+
+ else
+ {
+ if (pulse)
+ blip.AddDelta((uint)_machine.CurrentFrameCycle, (short)(_volume));
+ else
+ blip.AddDelta((uint)_machine.CurrentFrameCycle, -(short)(_volume));
+
+ lastVolume = _volume;
+ }
+
+ LastPulse = pulse;
+ }
+
+
+ public void StartFrame()
+ {
+ }
+
+ public void EndFrame()
+ {
+ }
+
+ #endregion
+
+
+ #region ISoundProvider
+
+ public bool CanProvideAsync => false;
+
+ public SyncSoundMode SyncMode => SyncSoundMode.Sync;
+
+ public void SetSyncMode(SyncSoundMode mode)
+ {
+ if (mode != SyncSoundMode.Sync)
+ throw new InvalidOperationException("Only Sync mode is supported.");
+ }
+
+ public void GetSamplesAsync(short[] samples)
+ {
+ throw new NotSupportedException("Async is not available");
+ }
+
+ public void DiscardSamples()
+ {
+ blip.Clear();
+ }
+
+ public void GetSamplesSync(out short[] samples, out int nsamp)
+ {
+ blip.EndFrame((uint)_tStatesPerFrame);
+ nsamp = blip.SamplesAvailable();
+ samples = new short[nsamp * 2];
+ blip.ReadSamples(samples, nsamp, true);
+ for (int i = 0; i < nsamp * 2; i += 2)
+ {
+ samples[i + 1] = samples[i];
+ }
+ }
+
+ #endregion
+
+ #region State Serialization
+
+ public void SyncState(Serializer ser)
+ {
+ ser.BeginSection("Buzzer");
+ ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame);
+ ser.Sync("_sampleRate", ref _sampleRate);
+ ser.Sync("LastPulse", ref LastPulse);
+ ser.Sync("LastPulseTState", ref LastPulseTState);
+ ser.EndSection();
+ }
+
+ #endregion
+ }
+}
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs
index 1f6b9ad20c..7a12444892 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
+ /*
///
/// Represents the piezoelectric buzzer used in the Spectrum to produce sound
/// The beeper is controlled by rapidly toggling bit 4 of port &FE
@@ -146,8 +147,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
_sampleRate = sampleRate;
_tStatesPerFrame = tStatesPerFrame;
- _tStatesPerSample = 79;
- _samplesPerFrame = (int)_tStatesPerFrame / _tStatesPerSample;
+ _tStatesPerSample = 99; // 79;
+ _samplesPerFrame = 705; // 882; // (int)_tStatesPerFrame / _tStatesPerSample;
Pulses = new List(1000);
}
@@ -297,7 +298,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
samples = stereoBuffer;
- nsamp = _samplesPerFrame; // soundBufferContains;
+ nsamp = soundBufferContains; // _samplesPerFrame; // soundBufferContains;
}
#endregion
@@ -319,4 +320,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
#endregion
}
+
+ */
}
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs
index f3ff00fd09..7ba831e9a7 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs
@@ -152,8 +152,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (_renderSound)
{
- BuzzerDevice.StartFrame();
- TapeBuzzer.StartFrame();
+ //BuzzerDevice.StartFrame();
+ //TapeBuzzer.StartFrame();
if (AYDevice != null)
AYDevice.StartFrame();
@@ -183,8 +183,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (_renderSound)
{
- BuzzerDevice.EndFrame();
- TapeBuzzer.EndFrame();
+ //BuzzerDevice.EndFrame();
+ //TapeBuzzer.EndFrame();
}
if (AYDevice != null)
@@ -365,7 +365,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (AYDevice != null)
{
AYDevice.SyncState(ser);
- ((AYChip)AYDevice as AYChip).PanningConfiguration = Spectrum.Settings.AYPanConfig;
+ ((AY38912)AYDevice as AY38912).PanningConfiguration = Spectrum.Settings.AYPanConfig;
}
ser.Sync("tapeMediaIndex", ref tapeMediaIndex);
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs
index 79479b4789..7f479eb635 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs
@@ -552,13 +552,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public int VsyncNumerator
{
- get { return ClockSpeed; }
+ get { return ClockSpeed * 50; }// ClockSpeed; }
set { }
}
public int VsyncDenominator
{
- get { return FrameLength; }
+ get { return ClockSpeed; }//FrameLength; }
}
public int[] GetVideoBuffer()
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs
index 3ef13c4559..74b6744893 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs
@@ -28,13 +28,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
ULADevice = new ULA128(this);
- BuzzerDevice = new Buzzer(this);
+ BuzzerDevice = new Beeper(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);
- TapeBuzzer = new Buzzer(this);
+ TapeBuzzer = new Beeper(this);
TapeBuzzer.Init(44100, ULADevice.FrameLength);
- AYDevice = new AYChip(this);
+ AYDevice = new AY38912(this);
AYDevice.Init(44100, ULADevice.FrameLength);
KeyboardDevice = new StandardKeyboard(this);
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs
index ed9d65282c..a6eec74beb 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs
@@ -28,13 +28,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
ULADevice = new ULAPlus2a(this);
- BuzzerDevice = new Buzzer(this);
+ BuzzerDevice = new Beeper(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);
- TapeBuzzer = new Buzzer(this);
+ TapeBuzzer = new Beeper(this);
TapeBuzzer.Init(44100, ULADevice.FrameLength);
- AYDevice = new AYChip(this);
+ AYDevice = new AY38912(this);
AYDevice.Init(44100, ULADevice.FrameLength);
KeyboardDevice = new StandardKeyboard(this);
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs
index 9cb24e8e5a..5b000e543e 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs
@@ -28,13 +28,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
ULADevice = new ULAPlus3(this);
- BuzzerDevice = new Buzzer(this);
+ BuzzerDevice = new Beeper(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);
- TapeBuzzer = new Buzzer(this);
+ TapeBuzzer = new Beeper(this);
TapeBuzzer.Init(44100, ULADevice.FrameLength);
- AYDevice = new AYChip(this);
+ AYDevice = new AY38912(this);
AYDevice.Init(44100, ULADevice.FrameLength);
KeyboardDevice = new StandardKeyboard(this);
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs
index 98968ff315..db8105555d 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs
@@ -23,10 +23,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
ULADevice = new ULA48(this);
- BuzzerDevice = new Buzzer(this);
+ BuzzerDevice = new Beeper(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);
- TapeBuzzer = new Buzzer(this);
+ TapeBuzzer = new Beeper(this);
TapeBuzzer.Init(44100, ULADevice.FrameLength);
KeyboardDevice = new StandardKeyboard(this);
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs
index 6e954e3430..0c76d3e863 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs
@@ -28,16 +28,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// restore user settings to devices
if (_machine != null && _machine.AYDevice != null)
{
- ((AYChip)_machine.AYDevice as AYChip).PanningConfiguration = o.AYPanConfig;
+ ((AY38912)_machine.AYDevice as AY38912).PanningConfiguration = o.AYPanConfig;
_machine.AYDevice.Volume = o.AYVolume;
}
if (_machine != null && _machine.BuzzerDevice != null)
{
- ((Buzzer)_machine.BuzzerDevice as Buzzer).Volume = o.EarVolume;
+ ((Beeper)_machine.BuzzerDevice as Beeper).Volume = o.EarVolume;
}
if (_machine != null && _machine.TapeBuzzer != null)
{
- ((Buzzer)_machine.TapeBuzzer as Buzzer).Volume = o.TapeVolume;
+ ((Beeper)_machine.TapeBuzzer as Beeper).Volume = o.TapeVolume;
}
Settings = o;
@@ -56,8 +56,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
[DisplayName("AY-3-8912 Panning Config")]
[Description("Set the PSG panning configuration.\nThe chip has 3 audio channels that can be outputed in different configurations")]
- [DefaultValue(AYChip.AYPanConfig.ABC)]
- public AYChip.AYPanConfig AYPanConfig { get; set; }
+ [DefaultValue(AY38912.AYPanConfig.ABC)]
+ public AY38912.AYPanConfig AYPanConfig { get; set; }
[DisplayName("Core OSD Message Verbosity")]
[Description("Full: Display all GUI messages\nMedium: Display only emulator/device generated messages\nNone: Show no messages")]
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs
index ea4c7df76d..01b4c6d4fb 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs
@@ -103,23 +103,24 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
SoundMixer.AddSource(_machine.AYDevice);
// set audio device settings
- if (_machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AYChip))
+ if (_machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AY38912))
{
- ((AYChip)_machine.AYDevice as AYChip).PanningConfiguration = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYPanConfig;
+ ((AY38912)_machine.AYDevice as AY38912).PanningConfiguration = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYPanConfig;
_machine.AYDevice.Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYVolume;
}
if (_machine.BuzzerDevice != null)
{
- ((Buzzer)_machine.BuzzerDevice as Buzzer).Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).EarVolume;
+ ((Beeper)_machine.BuzzerDevice as Beeper).Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).EarVolume;
}
if (_machine.TapeBuzzer != null)
{
- ((Buzzer)_machine.TapeBuzzer as Buzzer).Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).TapeVolume;
+ ((Beeper)_machine.TapeBuzzer as Beeper).Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).TapeVolume;
}
ser.Register(SoundMixer);
+ //ser.Register < ISoundProvider>(((ISoundProvider)_machine.BuzzerDevice));
HardReset();