New AY-3-8912 implementation. Better sounding and more performant
This commit is contained in:
parent
8234b2acfa
commit
7a36f913ec
|
@ -263,7 +263,7 @@
|
|||
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\SinclairJoystick2.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\SinclairJoystick1.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\NullJoystick.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\SoundOuput\AY38912.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\SoundOuput\AYChip.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\SoundOuput\Buzzer.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\Datacorder\DatacorderDevice.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IKeyboard.cs" />
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// <summary>
|
||||
/// Represents a PSG device (in this case an AY-3-891x)
|
||||
/// </summary>
|
||||
public interface IPSG : ISoundProvider
|
||||
public interface IPSG : ISoundProvider, IPortIODevice
|
||||
{
|
||||
/// <summary>
|
||||
/// Initlization routine
|
||||
|
@ -24,7 +24,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// Activates a register
|
||||
/// </summary>
|
||||
int SelectedRegister { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes to the PSG
|
||||
/// </summary>
|
||||
|
@ -35,6 +35,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// Reads from the PSG
|
||||
/// </summary>
|
||||
int PortRead();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resets the PSG
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The final sample buffer
|
||||
/// </summary>
|
||||
private short[] _samples = new short[0];
|
||||
|
||||
/// <summary>
|
||||
/// Number of samples in one frame
|
||||
/// </summary>
|
||||
public int SamplesPerFrame
|
||||
{
|
||||
get { return _samplesPerFrame; }
|
||||
set { _samplesPerFrame = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of TStates in each sample
|
||||
/// </summary>
|
||||
public int TStatesPerSample
|
||||
{
|
||||
get { return _tStatesPerSample; }
|
||||
set { _tStatesPerSample = value; }
|
||||
}
|
||||
|
||||
#region Construction & Initialisation
|
||||
|
||||
public AY38912()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialises the AY chip
|
||||
/// </summary>
|
||||
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.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Register constants
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Channels
|
||||
/// </summary>
|
||||
internal enum Channel
|
||||
{
|
||||
A, B, C
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ACB configuration
|
||||
/// </summary>
|
||||
private int ChannelLeft = 0;
|
||||
private int ChannelRight = 1; //2 if ABC
|
||||
private int ChannelCenter = 2; //1 if ABC
|
||||
|
||||
/// <summary>
|
||||
/// Register storage
|
||||
/// </summary>
|
||||
private int[] regs = new int[16];
|
||||
|
||||
/// <summary>
|
||||
/// State
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Buffer arrays
|
||||
/// </summary>
|
||||
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];
|
||||
|
||||
/// <summary>
|
||||
/// Measurements from comp.sys.sinclair (2001 Matthew Westcott)
|
||||
/// </summary>
|
||||
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
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Volume storage (short)
|
||||
/// </summary>
|
||||
private short[] AY_SpecVolumes = new short[16];
|
||||
|
||||
/// <summary>
|
||||
/// Sets the ACB configuration
|
||||
/// </summary>
|
||||
/// <param name="val"></param>
|
||||
public void SetSpeakerACB(bool val)
|
||||
{
|
||||
// ACB
|
||||
if (val)
|
||||
{
|
||||
ChannelCenter = 2;
|
||||
ChannelRight = 1;
|
||||
}
|
||||
// ABC
|
||||
else
|
||||
{
|
||||
ChannelCenter = 1;
|
||||
ChannelRight = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility method to set all registers externally
|
||||
/// </summary>
|
||||
/// <param name="_regs"></param>
|
||||
public void SetRegisters(byte[] _regs)
|
||||
{
|
||||
for (int f = 0; f < 16; f++)
|
||||
regs[f] = _regs[f];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility method to get all registers externally
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public byte[] GetRegisters()
|
||||
{
|
||||
byte[] newArray = new byte[16];
|
||||
for (int f = 0; f < 16; f++)
|
||||
newArray[f] = (byte)(regs[f] & 0xff);
|
||||
return newArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selected Register property
|
||||
/// </summary>
|
||||
public int SelectedRegister
|
||||
{
|
||||
get { return selectedRegister; }
|
||||
set { if (value < 16) selectedRegister = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates a port write to the AY chip
|
||||
/// </summary>
|
||||
/// <param name="val"></param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates port reads from the AY chip
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tone period for the specified channel
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <returns></returns>
|
||||
private int TonePeriod(int channel)
|
||||
{
|
||||
return (regs[(channel) << 1] | ((regs[((channel) << 1) | 1] & 0x0f) << 8));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the noise period for the specified channel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private int NoisePeriod()
|
||||
{
|
||||
return (regs[AY_NOISEPER] & 0x1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the envelope period for the specified channel
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private int EnvelopePeriod()
|
||||
{
|
||||
return ((regs[AY_E_FINE] | (regs[AY_E_COARSE] << 8)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the noise enable value for the specified channel
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <returns></returns>
|
||||
private int NoiseEnable(int channel)
|
||||
{
|
||||
return ((regs[AY_ENABLE] >> (3 + channel)) & 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tone enable value for the specified channel
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <returns></returns>
|
||||
private int ToneEnable(int channel)
|
||||
{
|
||||
return ((regs[AY_ENABLE] >> (channel)) & 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tone envelope value for the specified channel
|
||||
/// </summary>
|
||||
/// <param name="channel"></param>
|
||||
/// <returns></returns>
|
||||
private int ToneEnvelope(int channel)
|
||||
{
|
||||
//return ((regs[AY_A_VOL + channel] & 0x10) >> 4);
|
||||
return ((regs[AY_A_VOL + channel] >> 4) & 0x1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates noise
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates envelope
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,802 @@
|
|||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public class AYChip : IPSG
|
||||
{
|
||||
#region Device Fields
|
||||
|
||||
/// <summary>
|
||||
/// The emulated machine (passed in via constructor)
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Main constructor
|
||||
/// </summary>
|
||||
public AYChip(SpectrumBase machine)
|
||||
{
|
||||
_machine = machine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialises the AY chip
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// AY mixer panning configuration
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum AYPanConfig
|
||||
{
|
||||
MONO = 0,
|
||||
ABC = 1,
|
||||
ACB = 2,
|
||||
BAC = 3,
|
||||
BCA = 4,
|
||||
CAB = 5,
|
||||
CBA = 6,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The AY panning configuration
|
||||
/// </summary>
|
||||
public AYPanConfig PanningConfiguration
|
||||
{
|
||||
get
|
||||
{
|
||||
return _currentPanTab;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != _currentPanTab)
|
||||
{
|
||||
_currentPanTab = value;
|
||||
UpdateVolume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The AY chip output volume
|
||||
/// (0 - 100)
|
||||
/// </summary>
|
||||
public int Volume
|
||||
{
|
||||
get
|
||||
{
|
||||
return _volume;
|
||||
}
|
||||
set
|
||||
{
|
||||
value = Math.Max(0, value);
|
||||
value = Math.Max(100, value);
|
||||
if (_volume == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_volume = value;
|
||||
UpdateVolume();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected register
|
||||
/// </summary>
|
||||
public int SelectedRegister
|
||||
{
|
||||
get { return _activeRegister; }
|
||||
set
|
||||
{
|
||||
_activeRegister = (byte)value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Resets the PSG
|
||||
/// </summary>
|
||||
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);
|
||||
*/
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the value from the currently selected register
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int PortRead()
|
||||
{
|
||||
return _registers[_activeRegister];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes to the currently selected register
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start of frame
|
||||
/// </summary>
|
||||
public void StartFrame()
|
||||
{
|
||||
_audioBufferIndex = 0;
|
||||
BufferUpdate(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End of frame
|
||||
/// </summary>
|
||||
public void EndFrame()
|
||||
{
|
||||
BufferUpdate(_tStatesPerFrame);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the audiobuffer based on the current frame t-state
|
||||
/// </summary>
|
||||
/// <param name="frameCycle"></param>
|
||||
public void UpdateSound(int frameCycle)
|
||||
{
|
||||
BufferUpdate(frameCycle);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
/// <summary>
|
||||
/// Register indicies
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
*/
|
||||
/// </summary>
|
||||
private int[] _registers = new int[16];
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected register
|
||||
/// </summary>
|
||||
private byte _activeRegister;
|
||||
|
||||
/// <summary>
|
||||
/// The frequency of the AY chip
|
||||
/// </summary>
|
||||
private static int _chipFrequency = 1773400;
|
||||
|
||||
/// <summary>
|
||||
/// The rendering resolution of the chip
|
||||
/// </summary>
|
||||
private double _resolution = 50D * 8D / _chipFrequency;
|
||||
|
||||
/// <summary>
|
||||
/// Channel generator state
|
||||
/// </summary>
|
||||
private int _bitA;
|
||||
private int _bitB;
|
||||
private int _bitC;
|
||||
|
||||
/// <summary>
|
||||
/// Envelope state
|
||||
/// </summary>
|
||||
private int _eState;
|
||||
|
||||
/// <summary>
|
||||
/// Envelope direction
|
||||
/// </summary>
|
||||
private int _eDirection;
|
||||
|
||||
/// <summary>
|
||||
/// Noise seed
|
||||
/// </summary>
|
||||
private int _noiseSeed;
|
||||
|
||||
/// <summary>
|
||||
/// Mixer state
|
||||
/// </summary>
|
||||
private int _bit0;
|
||||
private int _bit1;
|
||||
private int _bit2;
|
||||
private int _bit3;
|
||||
private int _bit4;
|
||||
private int _bit5;
|
||||
|
||||
/// <summary>
|
||||
/// Noise generator state
|
||||
/// </summary>
|
||||
private int _bitN;
|
||||
|
||||
/// <summary>
|
||||
/// Envelope masks
|
||||
/// </summary>
|
||||
private int _eMaskA;
|
||||
private int _eMaskB;
|
||||
private int _eMaskC;
|
||||
|
||||
/// <summary>
|
||||
/// Amplitudes
|
||||
/// </summary>
|
||||
private int _vA;
|
||||
private int _vB;
|
||||
private int _vC;
|
||||
|
||||
/// <summary>
|
||||
/// Channel gen counters
|
||||
/// </summary>
|
||||
private int _countA;
|
||||
private int _countB;
|
||||
private int _countC;
|
||||
|
||||
/// <summary>
|
||||
/// Envelope gen counter
|
||||
/// </summary>
|
||||
private int _countE;
|
||||
|
||||
/// <summary>
|
||||
/// Noise gen counter
|
||||
/// </summary>
|
||||
private int _countN;
|
||||
|
||||
/// <summary>
|
||||
/// Channel gen dividers
|
||||
/// </summary>
|
||||
private int _dividerA;
|
||||
private int _dividerB;
|
||||
private int _dividerC;
|
||||
|
||||
/// <summary>
|
||||
/// Envelope gen divider
|
||||
/// </summary>
|
||||
private int _dividerE;
|
||||
|
||||
/// <summary>
|
||||
/// Noise gen divider
|
||||
/// </summary>
|
||||
private int _dividerN;
|
||||
|
||||
/// <summary>
|
||||
/// Panning table list
|
||||
/// </summary>
|
||||
private static List<uint[]> PanTabs = new List<uint[]>
|
||||
{
|
||||
// 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 }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected panning configuration
|
||||
/// </summary>
|
||||
private AYPanConfig _currentPanTab = AYPanConfig.ABC;
|
||||
|
||||
/// <summary>
|
||||
/// The current volume
|
||||
/// </summary>
|
||||
private int _volume = 50;
|
||||
|
||||
/// <summary>
|
||||
/// Volume tables state
|
||||
/// </summary>
|
||||
private uint[][] _volumeTables;
|
||||
|
||||
/// <summary>
|
||||
/// Volume table to be used
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Forces an update of the volume tables
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes timing information for the frame
|
||||
/// </summary>
|
||||
/// <param name="sampleRate"></param>
|
||||
/// <param name="frameTactCount"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the audiobuffer based on the current frame t-state
|
||||
/// </summary>
|
||||
/// <param name="cycle"></param>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// State serialization
|
||||
/// </summary>
|
||||
/// <param name="ser"></param>
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -16,11 +16,69 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -16,11 +16,67 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// <returns></returns>
|
||||
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
|
|||
/// <param name="value"></param>
|
||||
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;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -16,14 +16,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<ISoundProvider>(SoundMixer);
|
||||
|
|
Loading…
Reference in New Issue