diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
index ecdd02e979..b9790199e8 100644
--- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
+++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
@@ -263,7 +263,7 @@
-
+
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs
index 8e8965d7b0..a6028aa2d6 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs
@@ -11,7 +11,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
///
/// Represents a PSG device (in this case an AY-3-891x)
///
- public interface IPSG : ISoundProvider
+ public interface IPSG : ISoundProvider, IPortIODevice
{
///
/// Initlization routine
@@ -24,7 +24,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// Activates a register
///
int SelectedRegister { get; set; }
-
+
///
/// Writes to the PSG
///
@@ -35,6 +35,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// Reads from the PSG
///
int PortRead();
+
///
/// Resets the PSG
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs
index 90bb8af1df..066570f6c9 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs
@@ -158,6 +158,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (_tapeIsPlaying)
return;
+ _machine.BuzzerDevice.SetTapeMode(true);
+
_machine.Spectrum.OSD_TapePlaying();
// update the lastCycle
@@ -210,6 +212,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (!_tapeIsPlaying)
return;
+ _machine.BuzzerDevice.SetTapeMode(false);
+
_machine.Spectrum.OSD_TapeStopped();
// sign that the tape is no longer playing
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs
index f4289edf92..0616003465 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs
@@ -339,6 +339,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
(for instance by XOR A/IN A,(FE)) is one, no key is pressed
*/
+ if ((port & 0x0001) != 0)
+ return false;
+
if ((port & 0x8000) == 0)
{
result &= KeyLine[7];
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs
deleted file mode 100644
index 62c3d7829b..0000000000
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs
+++ /dev/null
@@ -1,718 +0,0 @@
-
-using BizHawk.Common;
-using BizHawk.Emulation.Common;
-using System;
-
-namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
-{
- public class AY38912 : IPSG
- {
- private int _tStatesPerFrame;
- private int _sampleRate;
- private int _samplesPerFrame;
- private int _tStatesPerSample;
- private int _sampleCounter;
- const int AY_SAMPLE_RATE = 16;
- private int _AYCyclesPerFrame;
- private int _nsamp;
- private int _AYCount;
-
-
- ///
- /// The final sample buffer
- ///
- private short[] _samples = new short[0];
-
- ///
- /// Number of samples in one frame
- ///
- public int SamplesPerFrame
- {
- get { return _samplesPerFrame; }
- set { _samplesPerFrame = value; }
- }
-
- ///
- /// Number of TStates in each sample
- ///
- public int TStatesPerSample
- {
- get { return _tStatesPerSample; }
- set { _tStatesPerSample = value; }
- }
-
- #region Construction & Initialisation
-
- public AY38912()
- {
- Reset();
- }
-
- ///
- /// Initialises the AY chip
- ///
- public void Init(int sampleRate, int tStatesPerFrame)
- {
- _sampleRate = sampleRate;
- _tStatesPerFrame = tStatesPerFrame;
- _tStatesPerSample = 79;
- _samplesPerFrame = _tStatesPerFrame / _tStatesPerSample;
- _AYCyclesPerFrame = _tStatesPerFrame / AY_SAMPLE_RATE;
-
- _samples = new short[_samplesPerFrame * 2];
- _nsamp = _samplesPerFrame;
- }
-
- #endregion
-
- public void UpdateSound(int currentFrameCycle)
- {
- for (int i = 0; i < (currentFrameCycle / AY_SAMPLE_RATE) - _AYCount; i++)
- {
- Update();
- SampleAY();
- _AYCount++;
- }
-
- // calculate how many samples must be processed
- int samplesToGenerate = (currentFrameCycle / _tStatesPerSample) - (_sampleCounter / 2);
-
- // begin generation
- if (samplesToGenerate > 0)
- {
- // ensure the required resolution
- while (soundSampleCounter < 4)
- {
- SampleAY();
- }
- EndSampleAY();
-
- // generate needed samples
- for (int i = 0; i < samplesToGenerate; i++)
- {
- _samples[_sampleCounter++] = (short)(averagedChannelSamples[0]);
- _samples[_sampleCounter++] = (short)(averagedChannelSamples[1]);
-
- samplesToGenerate--;
- }
-
- averagedChannelSamples[0] = 0;
- averagedChannelSamples[1] = 0;
- averagedChannelSamples[2] = 0;
- }
- }
-
- public void StartFrame()
- {
- _AYCount = 0;
-
- // the stereo _samples buffer should already have been processed as a part of
- // ISoundProvider at the end of the last frame
- _sampleCounter = 0;
- }
-
- public void EndFrame()
- {
- }
-
-
- public void Reset()
- {
- // reset volumes
- for (int i = 0; i < 16; i++)
- AY_SpecVolumes[i] = (short)(AY_Volumes[i] * 8191);
-
- soundSampleCounter = 0;
- regs[AY_NOISEPER] = 0xFF;
- noiseOut = 0x01;
- envelopeVolume = 0;
- noiseCount = 0;
-
- // reset state of all channels
- for (int f = 0; f < 3; f++)
- {
- channel_count[f] = 0;
- channel_mix[f] = 0;
- channel_out[f] = 0;
- averagedChannelSamples[f] = 0;
- }
-
- envelopeCount = 0;
- randomSeed = 1;
- selectedRegister = 0;
- }
-
- #region IStatable
-
- public void SyncState(Serializer ser)
- {
- ser.BeginSection("AY38912");
- ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame);
- ser.Sync("_sampleRate", ref _sampleRate);
- ser.Sync("_samplesPerFrame", ref _samplesPerFrame);
- ser.Sync("_tStatesPerSample", ref _tStatesPerSample);
- ser.Sync("_sampleCounter", ref _sampleCounter);
-
- ser.Sync("ChannelLeft", ref ChannelLeft);
- ser.Sync("ChannelRight", ref ChannelRight);
- ser.Sync("ChannelCenter", ref ChannelCenter);
- ser.Sync("Regs", ref regs, false);
- ser.Sync("NoiseOut", ref noiseOut);
- ser.Sync("envelopeVolume", ref envelopeVolume);
- ser.Sync("noiseCount", ref noiseCount);
- ser.Sync("envelopeCount", ref envelopeCount);
- ser.Sync("randomSeed", ref randomSeed);
- ser.Sync("envelopeClock", ref envelopeClock);
- ser.Sync("selectedRegister", ref selectedRegister);
- ser.Sync("soundSampleCounter", ref soundSampleCounter);
- ser.Sync("stereoSound", ref stereoSound);
- ser.Sync("sustaining", ref sustaining);
- ser.Sync("sustain", ref sustain);
- ser.Sync("alternate", ref alternate);
- ser.Sync("attack", ref attack);
- ser.Sync("envelopeStep", ref envelopeStep);
-
- ser.Sync("channel_out", ref channel_out, false);
- ser.Sync("channel_count", ref channel_count, false);
- ser.Sync("averagedChannelSamples", ref averagedChannelSamples, false);
- ser.Sync("channel_mix", ref channel_mix, false);
-
- ser.Sync("AY_SpecVolumes", ref AY_SpecVolumes, false);
-
- ser.Sync("_samples", ref _samples, false);
- ser.Sync("_nsamp", ref _nsamp);
- ser.EndSection();
- }
-
- #endregion
-
- #region AY Sound Implementation
-
- /*
- Based on the AYSound class from ArjunNair's Zero-Emulator
- https://github.com/ArjunNair/Zero-Emulator/
- *MIT LICENSED*
-
- The MIT License (MIT)
- Copyright (c) 2009 Arjun Nair
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
- (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge,
- publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
- subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
- ///
- /// Register constants
- ///
- private const byte AY_A_FINE = 0;
- private const byte AY_A_COARSE = 1;
- private const byte AY_B_FINE = 2;
- private const byte AY_B_COARSE = 3;
- private const byte AY_C_FINE = 4;
- private const byte AY_C_COARSE = 5;
- private const byte AY_NOISEPER = 6;
- private const byte AY_ENABLE = 7;
- private const byte AY_A_VOL = 8;
- private const byte AY_B_VOL = 9;
- private const byte AY_C_VOL = 10;
- private const byte AY_E_FINE = 11;
- private const byte AY_E_COARSE = 12;
- private const byte AY_E_SHAPE = 13;
- private const byte AY_PORT_A = 14;
- private const byte AY_PORT_B = 15;
-
- ///
- /// Channels
- ///
- internal enum Channel
- {
- A, B, C
- }
-
- ///
- /// ACB configuration
- ///
- private int ChannelLeft = 0;
- private int ChannelRight = 1; //2 if ABC
- private int ChannelCenter = 2; //1 if ABC
-
- ///
- /// Register storage
- ///
- private int[] regs = new int[16];
-
- ///
- /// State
- ///
- private int noiseOut;
- private int envelopeVolume;
- private int noiseCount;
- private int envelopeCount;
- private ulong randomSeed;
- private byte envelopeClock = 0;
- private int selectedRegister;
- public ushort soundSampleCounter;
- private bool stereoSound = false;
- private bool sustaining;
- private bool sustain;
- private bool alternate;
- private int attack;
- private int envelopeStep;
-
- ///
- /// Buffer arrays
- ///
- private int[] channel_out = new int[3];
- private int[] channel_count = new int[3];
- private int[] averagedChannelSamples = new int[3];
- private short[] channel_mix = new short[3];
-
- ///
- /// Measurements from comp.sys.sinclair (2001 Matthew Westcott)
- ///
- private float[] AY_Volumes =
- {
- 0.0000f, 0.0137f, 0.0205f, 0.0291f,
- 0.0423f, 0.0618f, 0.0847f, 0.1369f,
- 0.1691f, 0.2647f, 0.3527f, 0.4499f,
- 0.5704f, 0.6873f, 0.8482f, 1.0000f
- };
-
- ///
- /// Volume storage (short)
- ///
- private short[] AY_SpecVolumes = new short[16];
-
- ///
- /// Sets the ACB configuration
- ///
- ///
- public void SetSpeakerACB(bool val)
- {
- // ACB
- if (val)
- {
- ChannelCenter = 2;
- ChannelRight = 1;
- }
- // ABC
- else
- {
- ChannelCenter = 1;
- ChannelRight = 2;
- }
- }
-
- ///
- /// Utility method to set all registers externally
- ///
- ///
- public void SetRegisters(byte[] _regs)
- {
- for (int f = 0; f < 16; f++)
- regs[f] = _regs[f];
- }
-
- ///
- /// Utility method to get all registers externally
- ///
- ///
- public byte[] GetRegisters()
- {
- byte[] newArray = new byte[16];
- for (int f = 0; f < 16; f++)
- newArray[f] = (byte)(regs[f] & 0xff);
- return newArray;
- }
-
- ///
- /// Selected Register property
- ///
- public int SelectedRegister
- {
- get { return selectedRegister; }
- set { if (value < 16) selectedRegister = value; }
- }
-
- ///
- /// Simulates a port write to the AY chip
- ///
- ///
- public void PortWrite(int val)
- {
- switch (SelectedRegister)
- {
- // not implemented / necessary
- case AY_A_FINE:
- case AY_B_FINE:
- case AY_C_FINE:
- case AY_E_FINE:
- case AY_E_COARSE:
- break;
-
- case AY_A_COARSE:
- case AY_B_COARSE:
- case AY_C_COARSE:
- val &= 0x0f;
- break;
-
- case AY_NOISEPER:
- case AY_A_VOL:
- case AY_B_VOL:
- case AY_C_VOL:
- val &= 0x1f;
- break;
-
- case AY_ENABLE:
- /*
- if ((lastEnable == -1) || ((lastEnable & 0x40) != (regs[AY_ENABLE] & 0x40))) {
- SelectedRegister = ((regs[AY_ENABLE] & 0x40) > 0 ? regs[AY_PORT_B] : 0xff);
- }
- if ((lastEnable == -1) || ((lastEnable & 0x80) != (regs[AY_ENABLE] & 0x80))) {
- PortWrite((regs[AY_ENABLE] & 0x80) > 0 ? regs[AY_PORT_B] : 0xff);
- }
- lastEnable = regs[AY_ENABLE];*/
- break;
-
- case AY_E_SHAPE:
- val &= 0x0f;
- attack = ((val & 0x04) != 0 ? 0x0f : 0x00);
- // envelopeCount = 0;
- if ((val & 0x08) == 0)
- {
- /* if Continue = 0, map the shape to the equivalent one which has Continue = 1 */
- sustain = true;
- alternate = (attack != 0);
- }
- else
- {
- sustain = (val & 0x01) != 0;
- alternate = (val & 0x02) != 0;
- }
- envelopeStep = 0x0f;
- sustaining = false;
- envelopeVolume = (envelopeStep ^ attack);
- break;
-
- case AY_PORT_A:
- /*
- if ((regs[AY_ENABLE] & 0x40) > 0) {
- selectedRegister = regs[AY_PORT_A];
- }*/
- break;
-
- case AY_PORT_B:
- /*
- if ((regs[AY_ENABLE] & 0x80) > 0) {
- PortWrite(regs[AY_PORT_A]);
- }*/
- break;
- }
-
- regs[SelectedRegister] = val;
- }
-
- ///
- /// Simulates port reads from the AY chip
- ///
- ///
- public int PortRead()
- {
- if (SelectedRegister == AY_PORT_B)
- {
- if ((regs[AY_ENABLE] & 0x80) == 0)
- return 0xff;
- else
- return regs[AY_PORT_B];
- }
-
- return regs[selectedRegister];
- }
-
- private void EndSampleAY()
- {
- //averagedChannelSamples[0] = (short)((averagedChannelSamples[ChannelLeft] +
- // averagedChannelSamples[ChannelCenter] +
- // averagedChannelSamples[ChannelRight])
- // / soundSampleCounter);
-
- // averagedChannelSamples[1] = (short)((averagedChannelSamples[ChannelLeft] +
- // averagedChannelSamples[ChannelCenter] +
- // averagedChannelSamples[ChannelRight])
- // / soundSampleCounter);
-
- averagedChannelSamples[0] = (short)((averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter] / 1.5) / soundSampleCounter);
- averagedChannelSamples[1] = (short)((averagedChannelSamples[ChannelRight] + averagedChannelSamples[ChannelCenter] / 1.5) / soundSampleCounter);
-
- soundSampleCounter = 0;
- }
-
- private void SampleAY()
- {
- int ah;
-
- ah = regs[AY_ENABLE];
-
- channel_mix[(int)Channel.A] = MixChannel(ah, regs[AY_A_VOL], (int)Channel.A);
-
- ah >>= 1;
- channel_mix[(int)Channel.B] = MixChannel(ah, regs[AY_B_VOL], (int)Channel.B);
-
- ah >>= 1;
- channel_mix[(int)Channel.C] = MixChannel(ah, regs[AY_C_VOL], (int)Channel.C);
-
- averagedChannelSamples[0] += channel_mix[(int)Channel.A];
- averagedChannelSamples[1] += channel_mix[(int)Channel.B];
- averagedChannelSamples[2] += channel_mix[(int)Channel.C];
- soundSampleCounter++;
- }
-
- private short MixChannel(int ah, int cl, int chan)
- {
- int al = channel_out[chan];
- int bl, bh;
- bl = ah;
- bh = ah;
- bh &= 0x1;
- bl >>= 3;
-
- al |= (bh); //Tone | AY_ENABLE
- bl |= (noiseOut); //Noise | AY_ENABLE
- al &= bl;
-
- if ((al != 0))
- {
- if ((cl & 16) != 0)
- cl = envelopeVolume;
-
- cl &= 15;
-
- //return (AY_Volumes[cl]);
- return (AY_SpecVolumes[cl]);
- }
- return 0;
- }
-
- ///
- /// Gets the tone period for the specified channel
- ///
- ///
- ///
- private int TonePeriod(int channel)
- {
- return (regs[(channel) << 1] | ((regs[((channel) << 1) | 1] & 0x0f) << 8));
- }
-
- ///
- /// Gets the noise period for the specified channel
- ///
- ///
- private int NoisePeriod()
- {
- return (regs[AY_NOISEPER] & 0x1f);
- }
-
- ///
- /// Gets the envelope period for the specified channel
- ///
- ///
- private int EnvelopePeriod()
- {
- return ((regs[AY_E_FINE] | (regs[AY_E_COARSE] << 8)));
- }
-
- ///
- /// Gets the noise enable value for the specified channel
- ///
- ///
- ///
- private int NoiseEnable(int channel)
- {
- return ((regs[AY_ENABLE] >> (3 + channel)) & 1);
- }
-
- ///
- /// Gets the tone enable value for the specified channel
- ///
- ///
- ///
- private int ToneEnable(int channel)
- {
- return ((regs[AY_ENABLE] >> (channel)) & 1);
- }
-
- ///
- /// Gets the tone envelope value for the specified channel
- ///
- ///
- ///
- private int ToneEnvelope(int channel)
- {
- //return ((regs[AY_A_VOL + channel] & 0x10) >> 4);
- return ((regs[AY_A_VOL + channel] >> 4) & 0x1);
- }
-
- ///
- /// Updates noise
- ///
- private void UpdateNoise()
- {
- noiseCount++;
- if (noiseCount >= NoisePeriod() && (noiseCount > 4))
- {
- /* Is noise output going to change? */
- if (((randomSeed + 1) & 2) != 0) /* (bit0^bit1)? */
- {
- noiseOut ^= 1;
- }
-
- /* The Random Number Generator of the 8910 is a 17-bit shift */
- /* register. The input to the shift register is bit0 XOR bit3 */
- /* (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips. */
-
- /* The following is a fast way to compute bit17 = bit0^bit3. */
- /* Instead of doing all the logic operations, we only check */
- /* bit0, relying on the fact that after three shifts of the */
- /* register, what now is bit3 will become bit0, and will */
- /* invert, if necessary, bit14, which previously was bit17. */
- if ((randomSeed & 1) != 0)
- randomSeed ^= 0x24000; /* This version is called the "Galois configuration". */
- randomSeed >>= 1;
- noiseCount = 0;
- }
- }
-
- ///
- /// Updates envelope
- ///
- private void UpdateEnvelope()
- {
- /* update envelope */
- if (!sustaining)
- {
- envelopeCount++;
- if ((envelopeCount >= EnvelopePeriod()))
- {
- envelopeStep--;
-
- /* check envelope current position */
- if (envelopeStep < 0)
- {
- if (sustain)
- {
- if (alternate)
- attack ^= 0x0f;
- sustaining = true;
- envelopeStep = 0;
- }
- else
- {
- /* if CountEnv has looped an odd number of times (usually 1), */
- /* invert the output. */
- if (alternate && ((envelopeStep & (0x0f + 1)) != 0) && (envelopeCount > 4))
- attack ^= 0x0f;
-
- envelopeStep &= 0x0f;
- }
- }
- envelopeCount = 0;
- }
- }
- envelopeVolume = (envelopeStep ^ attack);
- }
-
-
- public void Update()
- {
- envelopeClock ^= 1;
-
- if (envelopeClock == 1)
- {
- envelopeCount++;
-
- //if ((((regs[AY_A_VOL + 0] & 0x10) >> 4) & (((regs[AY_A_VOL + 1] & 0x10) >> 4) & ((regs[AY_A_VOL + 2] & 0x10) >> 4))) != 1)
- //if ((((regs[AY_A_VOL + 0] >> 4) & 0x1) & (((regs[AY_A_VOL + 1] >> 4) & 0x1) & ((regs[AY_A_VOL + 2] >> 4) & 0x1))) != 0)
- if (((regs[AY_A_VOL + 0] & 0x10) & (regs[AY_A_VOL + 1] & 0x10) & (regs[AY_A_VOL + 2] & 0x10)) != 1)
- {
- // update envelope
- if (!sustaining)
- UpdateEnvelope();
-
- envelopeVolume = (envelopeStep ^ attack);
- }
- }
-
- // update noise
- if ((regs[AY_ENABLE] & 0x38) != 0x38)
- {
- UpdateNoise();
- }
-
- // update channels
- channel_count[0]++;
- int regs1 = (regs[1] & 0x0f) << 8;
- if (((regs[0] | regs1) > 4) && (channel_count[0] >= (regs[0] | regs1)))
- {
- channel_out[0] ^= 1;
- channel_count[0] = 0;
- }
-
- int regs3 = (regs[3] & 0x0f) << 8;
- channel_count[1]++;
- if (((regs[2] | regs3) > 4) && (channel_count[1] >= (regs[2] | regs3)))
- {
- channel_out[1] ^= 1;
- channel_count[1] = 0;
- }
-
- int regs5 = (regs[5] & 0x0f) << 8;
- channel_count[2]++;
- if (((regs[4] | regs5) > 4) && (channel_count[2] >= (regs[4] | regs5)))
- {
- channel_out[2] ^= 1;
- channel_count[2] = 0;
- }
- }
-
-
- #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()
- {
-
- }
-
- public void GetSamplesSync(out short[] samples, out int nsamp)
- {
- samples = _samples;
- nsamp = _nsamp;
- }
-
- #endregion
-
- }
-}
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs
new file mode 100644
index 0000000000..06817afe19
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs
@@ -0,0 +1,802 @@
+
+using BizHawk.Common;
+using BizHawk.Emulation.Common;
+using System;
+using System.Collections.Generic;
+
+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 AYChip : 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 AYChip(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[] { 100,100, 100,100, 100,100 },
+ // 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 = 50;
+
+ ///
+ /// 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()
+ {
+ var vol = (ulong)0xFFFF * (ulong)_volume / 100UL;
+ _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;
+
+ _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;
+
+ var mask = (1 << _registers[AY_E_SHAPE]);
+
+ if ((_eState & ~31) != 0)
+ {
+ 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);
+ }
+
+ ser.EndSection();
+ }
+
+ #endregion
+ }
+}
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs
index d5c1fff9a6..3e719e72e2 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs
@@ -140,7 +140,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (_renderSound)
{
BuzzerDevice.StartFrame();
- if (AYDevice != null && !PagingDisabled)
+ if (AYDevice != null)
AYDevice.StartFrame();
}
@@ -155,11 +155,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
CPU.ExecuteOne();
// update AY
+ int ayCnt = 0;
if (_renderSound)
{
- if (AYDevice != null && !PagingDisabled)
+ if (AYDevice != null)
{
- AYDevice.UpdateSound(CurrentFrameCycle);
+ //AYDevice.UpdateSound(CurrentFrameCycle);
+ if (ayCnt > 10)
+ {
+ //AYDevice.UpdateSound(CurrentFrameCycle);
+ ayCnt = 0;
+ }
}
}
}
@@ -174,6 +180,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (_renderSound)
BuzzerDevice.EndFrame();
+ if (AYDevice != null)
+ AYDevice.EndFrame();
+
FrameCount++;
// setup for next frame
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs
index 130264a4c7..67e164dfe1 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs
@@ -16,11 +16,69 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
///
public override byte ReadPort(ushort port)
{
+ bool deviceAddressed = true;
+
// process IO contention
ContendPortAddress(port);
int result = 0xFF;
+ // check AY
+ if (AYDevice.ReadPort(port, ref result))
+ return (byte)result;
+
+ // Kempston joystick input takes priority over all other input
+ // if this is detected just return the kempston byte
+ if ((port & 0xe0) == 0 || (port & 0x20) == 0)
+ {
+ if (LocateUniqueJoystick(JoystickType.Kempston) != null)
+ return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine;
+
+ InputRead = true;
+ }
+ else
+ {
+ if (KeyboardDevice.ReadPort(port, ref result))
+ {
+ // not a lagframe
+ InputRead = true;
+
+ // tape loading monitor cycle
+ TapeDevice.MonitorRead();
+
+ // process tape INs
+ TapeDevice.ReadPort(port, ref result);
+ }
+ else
+ deviceAddressed = false;
+ }
+
+ if (!deviceAddressed)
+ {
+ // If this is an unused port the floating memory bus should be returned
+ // Floating bus is read on the previous cycle
+ int _tStates = CurrentFrameCycle - 1;
+
+ // if we are on the top or bottom border return 0xff
+ if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod))
+ {
+ result = 0xff;
+ }
+ else
+ {
+ if (ULADevice.floatingBusTable[_tStates] < 0)
+ {
+ result = 0xff;
+ }
+ else
+ {
+ result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]);
+ }
+ }
+ }
+
+ /*
+
// Check whether the low bit is reset
// Technically the ULA should respond to every even I/O address
bool lowBitReset = (port & 0x0001) == 0;
@@ -81,7 +139,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
}
}
-
+
+ */
+
return (byte)result;
}
@@ -102,6 +162,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
int currT = CPU.TotalExecutedCycles;
+ AYDevice.WritePort(port, value);
+
// paging
if (port == 0x7ffd)
{
@@ -164,7 +226,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
//TapeDevice.ProcessMicBit((value & MIC_BIT) != 0);
}
-
+ /*
// Active AY Register
if ((port & 0xc002) == 0xc000)
{
@@ -178,7 +240,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
AYDevice.PortWrite(value);
CPU.TotalExecutedCycles += 3;
- }
+ }
+ */
}
}
}
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs
index 5e657b0863..3ea955ddca 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs
@@ -31,7 +31,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
BuzzerDevice = new Buzzer(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);
- AYDevice = new AY38912();
+ AYDevice = new AYChip(this);
AYDevice.Init(44100, ULADevice.FrameLength);
KeyboardDevice = new StandardKeyboard(this);
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs
index 24ac1f5f97..3e8d97f36f 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs
@@ -16,11 +16,67 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
///
public override byte ReadPort(ushort port)
{
+ bool deviceAddressed = true;
+
// process IO contention
ContendPortAddress(port);
int result = 0xFF;
+ // check AY
+ if (AYDevice.ReadPort(port, ref result))
+ return (byte)result;
+
+ // Kempston joystick input takes priority over all other input
+ // if this is detected just return the kempston byte
+ if ((port & 0xe0) == 0 || (port & 0x20) == 0)
+ {
+ if (LocateUniqueJoystick(JoystickType.Kempston) != null)
+ return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine;
+
+ InputRead = true;
+ }
+ else
+ {
+ if (KeyboardDevice.ReadPort(port, ref result))
+ {
+ // not a lagframe
+ InputRead = true;
+
+ // tape loading monitor cycle
+ TapeDevice.MonitorRead();
+
+ // process tape INs
+ TapeDevice.ReadPort(port, ref result);
+ }
+ else
+ deviceAddressed = false;
+ }
+
+ if (!deviceAddressed)
+ {
+ // If this is an unused port the floating memory bus should be returned
+ // Floating bus is read on the previous cycle
+ int _tStates = CurrentFrameCycle - 1;
+
+ // if we are on the top or bottom border return 0xff
+ if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod))
+ {
+ result = 0xff;
+ }
+ else
+ {
+ if (ULADevice.floatingBusTable[_tStates] < 0)
+ {
+ result = 0xff;
+ }
+ else
+ {
+ result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]);
+ }
+ }
+ }
+ /*
// Check whether the low bit is reset
// Technically the ULA should respond to every even I/O address
bool lowBitReset = (port & 0x0001) == 0;
@@ -88,10 +144,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
else
result = 0xff;
}
- */
+ *//*
// if unused port the floating memory bus should be returned (still todo)
}
+ */
return (byte)result;
}
@@ -103,6 +160,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
///
public override void WritePort(ushort port, byte value)
{
+ // process IO contention
+ ContendPortAddress(port);
+
// get a BitArray of the port
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
// get a BitArray of the value byte
@@ -111,7 +171,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// Check whether the low bit is reset
bool lowBitReset = !portBits[0]; // (port & 0x01) == 0;
- ULADevice.Contend(port);
+ AYDevice.WritePort(port, value);
// port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set
if (port == 0x7ffd)
@@ -131,10 +191,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// portbit 4 is the LOW BIT of the ROM selection
ROMlow = bits[4];
- }
+ }
}
// port 0x1ffd - hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set
- else if (port == 0x1ffd)
+ if (port == 0x1ffd)
{
if (!PagingDisabled)
{
@@ -172,75 +232,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// bit 4 is the printer port strobe
PrinterPortStrobe = bits[4];
}
- /*
- // port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set
- if (!portBits[1] && !portBits[15] && portBits[14])
- {
- // paging (skip if paging has been disabled - paging can then only happen after a machine hard reset)
- if (!PagingDisabled)
- {
- // bit 0 specifies the paging mode
- SpecialPagingMode = bits[0];
-
- if (!SpecialPagingMode)
- {
- // we are in normal mode
- // portbit 4 is the LOW BIT of the ROM selection
- BitArray romHalfNibble = new BitArray(2);
- romHalfNibble[0] = portBits[4];
-
- // value bit 2 is the high bit of the ROM selection
- romHalfNibble[1] = bits[2];
-
- // value bit 1 is ignored in normal paging mode
-
- // set the ROMPage
- ROMPaged = ZXSpectrum.GetIntFromBitArray(romHalfNibble);
-
-
-
-
- // bit 3 controls shadow screen
- SHADOWPaged = bits[3];
-
- // Bit 5 set signifies that paging is disabled until next reboot
- PagingDisabled = bits[5];
- }
- }
- }
-
- // port 0x1ffd - special paging mode
- // hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set
- if (!portBits[1] && portBits[12] && !portBits[13] && !portBits[14] && !portBits[15])
- {
- if (!PagingDisabled && SpecialPagingMode)
- {
- // process special paging
- // this is decided based on combinations of bits 1 & 2
- // Config 0 = Bit1-0 Bit2-0
- // Config 1 = Bit1-1 Bit2-0
- // Config 2 = Bit1-0 Bit2-1
- // Config 3 = Bit1-1 Bit2-1
- BitArray confHalfNibble = new BitArray(2);
- confHalfNibble[0] = bits[1];
- confHalfNibble[1] = bits[2];
-
- // set special paging configuration
- PagingConfiguration = ZXSpectrum.GetIntFromBitArray(confHalfNibble);
-
- // last value should be saved at 0x5b67 (23399) - not sure if this is actually needed
- WriteBus(0x5b67, value);
- }
-
- // bit 3 controls the disk motor (1=on, 0=off)
- DiskMotorState = bits[3];
-
- // bit 4 is the printer port strobe
- PrinterPortStrobe = bits[4];
- }
-
- */
-
// Only even addresses address the ULA
if (lowBitReset)
@@ -268,100 +259,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
//TapeDevice.ProcessMicBit((value & MIC_BIT) != 0);
}
- else
- {
- // AY Register activation
- if ((port & 0xc002) == 0xc000)
- {
- var reg = value & 0x0f;
- AYDevice.SelectedRegister = reg;
- CPU.TotalExecutedCycles += 3;
- }
- else
- {
- if ((port & 0xC002) == 0x8000)
- {
- AYDevice.PortWrite(value);
- CPU.TotalExecutedCycles += 3;
- }
-
- /*
-
- else
- {
- if ((port & 0xC002) == 0x4000) //Are bits 1 and 15 reset and bit 14 set?
- {
- // memory paging activate
- if (PagingDisabled)
- return;
-
- // bit 5 handles paging disable (48k mode, persistent until next reboot)
- if ((value & 0x20) != 0)
- {
- PagingDisabled = true;
- }
-
- // shadow screen
- if ((value & 0x08) != 0)
- {
- SHADOWPaged = true;
- }
- else
- {
- SHADOWPaged = false;
- }
- }
- else
- {
- //Extra Memory Paging feature activate
- if ((port & 0xF002) == 0x1000) //Is bit 12 set and bits 13,14,15 and 1 reset?
- {
- if (PagingDisabled)
- return;
-
- // set disk motor state
- //todo
-
- if ((value & 0x08) != 0)
- {
- //diskDriveState |= (1 << 4);
- }
- else
- {
- //diskDriveState &= ~(1 << 4);
- }
-
- if ((value & 0x1) != 0)
- {
- // activate special paging mode
- SpecialPagingMode = true;
- PagingConfiguration = (value & 0x6 >> 1);
- }
- else
- {
- // normal paging mode
- SpecialPagingMode = false;
- }
- }
- else
- {
- // disk write port
- if ((port & 0xF002) == 0x3000) //Is bit 12 set and bits 13,14,15 and 1 reset?
- {
- //udpDrive.DiskWriteByte((byte)(val & 0xff));
- }
- }
- }
- }
- */
- }
- }
LastULAOutByte = value;
-
-
-
-
}
///
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs
index 88b672d174..3cd1f695d5 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs
@@ -31,7 +31,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
BuzzerDevice = new Buzzer(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);
- AYDevice = new AY38912();
+ AYDevice = new AYChip(this);
AYDevice.Init(44100, ULADevice.FrameLength);
KeyboardDevice = new StandardKeyboard(this);
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs
index 32bd26d842..bcbb31f35d 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs
@@ -16,14 +16,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
///
public override byte ReadPort(ushort port)
{
+ bool deviceAddressed = true;
+
// process IO contention
ContendPortAddress(port);
int result = 0xFF;
- // Check whether the low bit is reset
- // Technically the ULA should respond to every even I/O address
- bool lowBitReset = (port & 0x0001) == 0;
+ // check AY
+ if (AYDevice.ReadPort(port, ref result))
+ return (byte)result;
// Kempston joystick input takes priority over all other input
// if this is detected just return the kempston byte
@@ -34,62 +36,45 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
InputRead = true;
}
- else if (lowBitReset)
- {
- // Even I/O address so get input from keyboard
- KeyboardDevice.ReadPort(port, ref result);
-
- TapeDevice.MonitorRead();
-
- // not a lagframe
- InputRead = true;
-
- // tape loading monitor cycle
- TapeDevice.MonitorRead();
-
- // process tape INs
- TapeDevice.ReadPort(port, ref result);
- }
else
{
- // devices other than the ULA will respond here
- // (e.g. the AY sound chip in a 128k spectrum
-
- // AY register activate - on +3/2a both FFFD and BFFD active AY
- if ((port & 0xc002) == 0xc000)
+ if (KeyboardDevice.ReadPort(port, ref result))
{
- result = (int)AYDevice.PortRead();
+ // not a lagframe
+ InputRead = true;
+
+ // tape loading monitor cycle
+ TapeDevice.MonitorRead();
+
+ // process tape INs
+ TapeDevice.ReadPort(port, ref result);
}
- else if ((port & 0xc002) == 0x8000)
+ else
+ deviceAddressed = false;
+ }
+
+ if (!deviceAddressed)
+ {
+ // If this is an unused port the floating memory bus should be returned
+ // Floating bus is read on the previous cycle
+ int _tStates = CurrentFrameCycle - 1;
+
+ // if we are on the top or bottom border return 0xff
+ if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod))
{
- result = (int)AYDevice.PortRead();
+ result = 0xff;
}
-
- // Kempston Mouse (not implemented yet)
-
-
- else if ((port & 0xF002) == 0x2000) //Is bit 12 set and bits 13,14,15 and 1 reset?
+ else
{
- //result = udpDrive.DiskStatusRead();
-
- // disk drive is not yet implemented - return a max status byte for the menu to load
- result = 255;
- }
- else if ((port & 0xF002) == 0x3000)
- {
- //result = udpDrive.DiskReadByte();
- result = 0;
- }
-
- else if ((port & 0xF002) == 0x0)
- {
- if (PagingDisabled)
- result = 0x1;
- else
+ if (ULADevice.floatingBusTable[_tStates] < 0)
+ {
result = 0xff;
+ }
+ else
+ {
+ result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]);
+ }
}
-
- // if unused port the floating memory bus should be returned (still todo)
}
return (byte)result;
@@ -113,6 +98,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// Check whether the low bit is reset
bool lowBitReset = !portBits[0]; // (port & 0x01) == 0;
+ AYDevice.WritePort(port, value);
+
// port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set
if (port == 0x7ffd)
{
@@ -134,7 +121,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
}
// port 0x1ffd - hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set
- else if (port == 0x1ffd)
+ if (port == 0x1ffd)
{
if (!PagingDisabled)
{
@@ -199,25 +186,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
//TapeDevice.ProcessMicBit((value & MIC_BIT) != 0);
}
- else
- {
- // AY Register activation
- if ((port & 0xc002) == 0xc000)
- {
- var reg = value & 0x0f;
- AYDevice.SelectedRegister = reg;
- CPU.TotalExecutedCycles += 3;
- }
- else
- {
- if ((port & 0xC002) == 0x8000)
- {
- AYDevice.PortWrite(value);
- CPU.TotalExecutedCycles += 3;
- }
- }
- }
-
+
LastULAOutByte = value;
}
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs
index b9038e3af1..916232bcb6 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs
@@ -31,7 +31,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
BuzzerDevice = new Buzzer(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);
- AYDevice = new AY38912();
+ AYDevice = new AYChip(this);
AYDevice.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 059a2fcaa1..c3204f1c69 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs
@@ -24,8 +24,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public bool PutSettings(ZXSpectrumSettings o)
{
- if (SoundMixer != null)
- SoundMixer.Stereo = o.StereoSound;
+ //if (SoundMixer != null)
+ //SoundMixer.Stereo = o.StereoSound;
+
+ if (_machine != null && _machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AYChip))
+ {
+ ((AYChip)_machine.AYDevice as AYChip).PanningConfiguration = o.AYPanConfig;
+ }
Settings = o;
@@ -48,10 +53,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
[DefaultValue(true)]
public bool AutoLoadTape { get; set; }
+ /*
[DisplayName("Stereo Sound")]
[Description("Turn stereo sound on or off")]
[DefaultValue(true)]
public bool StereoSound { get; set; }
+ */
+
+ [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; }
[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.IStatable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs
index bbd45c713d..01419fd093 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs
@@ -51,8 +51,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
core = ms.ToArray();
}
-
-
if (ser.IsWriter)
{
ser.SyncEnum("_machineType", ref _machineType);
@@ -70,12 +68,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
var tmpM = _machineType;
ser.SyncEnum("_machineType", ref _machineType);
- if (tmpM != _machineType)
+ if (tmpM != _machineType && _machineType.ToString() != "72")
{
string msg = "SAVESTATE FAILED TO LOAD!!\n\n";
- msg += "Current Configuration: " + _machineType.ToString();
+ msg += "Current Configuration: " + tmpM.ToString();
msg += "\n";
- msg += "Saved Configuration: " + tmpM.ToString();
+ msg += "Saved Configuration: " + _machineType.ToString();
msg += "\n\n";
msg += "If you wish to load this SaveState ensure that you have the correct machine configuration selected, reboot the core, then try again.";
CoreComm.ShowMessage(msg);
diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs
index 27eb361a88..7f72bf4b4e 100644
--- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs
+++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs
@@ -101,7 +101,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
SoundMixer = new SoundProviderMixer((int)(32767 / 10), (ISoundProvider)_machine.BuzzerDevice);
if (_machine.AYDevice != null)
SoundMixer.AddSource(_machine.AYDevice);
- SoundMixer.Stereo = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).StereoSound;
+ //SoundMixer.Stereo = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).StereoSound;
+
+ if (_machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AYChip))
+ {
+ ((AYChip)_machine.AYDevice as AYChip).PanningConfiguration = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYPanConfig;
+ }
ser.Register(SoundMixer);