812 lines
25 KiB
C#
812 lines
25 KiB
C#
using BizHawk.Common;
|
|
using BizHawk.Emulation.Common;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|
{
|
|
/// <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 AY38912 : 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 AY38912(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[] { 50,50, 50,50, 50,50 },
|
|
// ABC
|
|
new uint[] { 100,10, 66,66, 10,100 },
|
|
// ACB
|
|
new uint[] { 100,10, 10,100, 66,66 },
|
|
// BAC
|
|
new uint[] { 66,66, 100,10, 10,100 },
|
|
// BCA
|
|
new uint[] { 10,100, 100,10, 66,66 },
|
|
// CAB
|
|
new uint[] { 66,66, 10,100, 100,10 },
|
|
// CBA
|
|
new uint[] { 10,100, 66,66, 100,10 }
|
|
};
|
|
|
|
/// <summary>
|
|
/// The currently selected panning configuration
|
|
/// </summary>
|
|
private AYPanConfig _currentPanTab = AYPanConfig.ABC;
|
|
|
|
/// <summary>
|
|
/// The current volume
|
|
/// </summary>
|
|
private int _volume = 75;
|
|
|
|
/// <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()
|
|
{
|
|
int upperFloor = 40000;
|
|
var inc = (0xFFFF - upperFloor) / 100;
|
|
|
|
var vol = inc * _volume; // ((ulong)0xFFFF * (ulong)_volume / 100UL) - 20000 ;
|
|
_volumeTables = new uint[6][];
|
|
|
|
// parent array
|
|
for (int j = 0; j < _volumeTables.Length; j++)
|
|
{
|
|
_volumeTables[j] = new uint[32];
|
|
|
|
// child array
|
|
for (int i = 0; i < _volumeTables[j].Length; i++)
|
|
{
|
|
_volumeTables[j][i] = (uint)(
|
|
(PanTabs[(int)_currentPanTab][j] * AYVolumes[i] * vol) /
|
|
(3 * 65535 * 100));
|
|
}
|
|
}
|
|
}
|
|
|
|
private int mult_const;
|
|
|
|
/// <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;
|
|
_samplesPerFrame = 882;
|
|
|
|
_tStatesPerSample = 79; //(int)Math.Round(((double)_tStatesPerFrame * 50D) /
|
|
//(16D * (double)_sampleRate),
|
|
//MidpointRounding.AwayFromZero);
|
|
|
|
//_samplesPerFrame = _tStatesPerFrame / _tStatesPerSample;
|
|
_audioBuffer = new short[_samplesPerFrame * 2]; //[_sampleRate / 50];
|
|
_audioBufferIndex = 0;
|
|
|
|
mult_const = ((_chipFrequency / 8) << 14) / _machine.ULADevice.ClockSpeed;
|
|
|
|
var aytickspercputick = (double)_machine.ULADevice.ClockSpeed / (double)_chipFrequency;
|
|
int ayCyclesPerSample = (int)((double)_tStatesPerSample * (double)aytickspercputick);
|
|
}
|
|
|
|
/// <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;
|
|
|
|
if ((_eState & ~31) != 0)
|
|
{
|
|
var mask = (1 << _registers[AY_E_SHAPE]);
|
|
|
|
if ((mask & ((1 << 0) | (1 << 1) | (1 << 2) |
|
|
(1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) |
|
|
(1 << 7) | (1 << 9) | (1 << 15))) != 0)
|
|
{
|
|
_eState = _eDirection = 0;
|
|
}
|
|
else if ((mask & ((1 << 8) | (1 << 12))) != 0)
|
|
{
|
|
_eState &= 31;
|
|
}
|
|
else if ((mask & ((1 << 10) | (1 << 14))) != 0)
|
|
{
|
|
_eDirection = -_eDirection;
|
|
_eState += _eDirection;
|
|
}
|
|
else
|
|
{
|
|
// 11,13
|
|
_eState = 31;
|
|
_eDirection = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// mix the sample
|
|
var mixA = ((_eMaskA & _eState) | _vA) & ((_bitA | _bit0) & (_bitN | _bit3));
|
|
var mixB = ((_eMaskB & _eState) | _vB) & ((_bitB | _bit1) & (_bitN | _bit4));
|
|
var mixC = ((_eMaskC & _eState) | _vC) & ((_bitC | _bit2) & (_bitN | _bit5));
|
|
|
|
var l = _volumeTables[0][mixA];
|
|
var r = _volumeTables[1][mixA];
|
|
|
|
l += _volumeTables[2][mixB];
|
|
r += _volumeTables[3][mixB];
|
|
l += _volumeTables[4][mixC];
|
|
r += _volumeTables[5][mixC];
|
|
|
|
_audioBuffer[_audioBufferIndex * 2] = (short)l;
|
|
_audioBuffer[(_audioBufferIndex * 2) + 1] = (short)r;
|
|
|
|
_audioBufferIndex++;
|
|
}
|
|
|
|
_lastStateRendered = cycle;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region ISoundProvider
|
|
|
|
public bool CanProvideAsync => false;
|
|
|
|
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
|
|
|
public void SetSyncMode(SyncSoundMode mode)
|
|
{
|
|
if (mode != SyncSoundMode.Sync)
|
|
throw new InvalidOperationException("Only Sync mode is supported.");
|
|
}
|
|
|
|
public void GetSamplesAsync(short[] samples)
|
|
{
|
|
throw new NotSupportedException("Async is not available");
|
|
}
|
|
|
|
public void DiscardSamples()
|
|
{
|
|
_audioBuffer = new short[_samplesPerFrame * 2];
|
|
}
|
|
|
|
public void GetSamplesSync(out short[] samples, out int nsamp)
|
|
{
|
|
nsamp = _samplesPerFrame;
|
|
samples = _audioBuffer;
|
|
DiscardSamples();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region State Serialization
|
|
|
|
public int nullDump = 0;
|
|
|
|
/// <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);
|
|
}
|
|
|
|
if (ser.IsReader)
|
|
_volume = _machine.Spectrum.Settings.AYVolume;
|
|
|
|
ser.EndSection();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|