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);