ZXHawk: Buzzer/Beeper implementation now use BlipBuffer - #1158
This commit is contained in:
parent
a21a11b70e
commit
9c800cf133
BizHawk.Client.EmuHawk/config/ZXSpectrum
BizHawk.Emulation.Cores
BizHawk.Emulation.Cores.csproj
Computers/SinclairSpectrum
Hardware/SoundOuput
Machine
ZXSpectrum.ISettable.csZXSpectrum.cs
|
@ -22,7 +22,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
_settings = ((ZXSpectrum)Global.Emulator).GetSettings().Clone();
|
||||
|
||||
// AY panning config
|
||||
var panTypes = Enum.GetNames(typeof(AYChip.AYPanConfig));
|
||||
var panTypes = Enum.GetNames(typeof(AY38912.AYPanConfig));
|
||||
foreach (var val in panTypes)
|
||||
{
|
||||
panTypecomboBox1.Items.Add(val);
|
||||
|
@ -51,7 +51,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
|
||||
if (changed)
|
||||
{
|
||||
_settings.AYPanConfig = (AYChip.AYPanConfig)Enum.Parse(typeof(AYChip.AYPanConfig), panTypecomboBox1.SelectedItem.ToString());
|
||||
_settings.AYPanConfig = (AY38912.AYPanConfig)Enum.Parse(typeof(AY38912.AYPanConfig), panTypecomboBox1.SelectedItem.ToString());
|
||||
|
||||
_settings.TapeVolume = tapeVolumetrackBar.Value;
|
||||
_settings.EarVolume = earVolumetrackBar.Value;
|
||||
|
|
|
@ -259,6 +259,8 @@
|
|||
<Compile Include="Computers\Commodore64\SaveState.cs" />
|
||||
<Compile Include="Computers\Commodore64\User\UserPortDevice.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IFDDHost.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\SoundOuput\AY38912.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\SoundOuput\Beeper.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Media\Disk\FloppyDisk.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IJoystick.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IPortIODevice.cs" />
|
||||
|
|
|
@ -0,0 +1,811 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <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
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/*
|
||||
/// <summary>
|
||||
/// AY-3-8912 Emulated Device
|
||||
///
|
||||
|
@ -177,31 +178,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// </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>
|
||||
|
@ -394,6 +371,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
The AY-3-8912 ignores bit 7 of this register.
|
||||
*/
|
||||
/*
|
||||
/// </summary>
|
||||
private int[] _registers = new int[16];
|
||||
|
||||
|
@ -585,12 +563,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
{
|
||||
_sampleRate = sampleRate;
|
||||
_tStatesPerFrame = frameTactCount;
|
||||
_samplesPerFrame = 882;
|
||||
|
||||
_tStatesPerSample = 79; //(int)Math.Round(((double)_tStatesPerFrame * 50D) /
|
||||
//(16D * (double)_sampleRate),
|
||||
//MidpointRounding.AwayFromZero);
|
||||
|
||||
_samplesPerFrame = _tStatesPerFrame / _tStatesPerSample;
|
||||
//_samplesPerFrame = _tStatesPerFrame / _tStatesPerSample;
|
||||
_audioBuffer = new short[_samplesPerFrame * 2]; //[_sampleRate / 50];
|
||||
_audioBufferIndex = 0;
|
||||
|
||||
|
@ -805,4 +784,5 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
#endregion
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Logical Beeper class
|
||||
/// Represents the piezoelectric buzzer used in the Spectrum to produce sound
|
||||
/// The beeper is controlled by rapidly toggling bit 4 of port &FE
|
||||
///
|
||||
/// It is instantiated twice, once for speccy beeper output, and once tape buzzer emulation
|
||||
///
|
||||
/// This implementation uses BlipBuffer and should *always* output at 44100 with 882 samples per frame
|
||||
/// (so that it can be mixed easily further down the line)
|
||||
/// </summary>
|
||||
public class Beeper : ISoundProvider, IBeeperDevice
|
||||
{
|
||||
#region Fields and Properties
|
||||
|
||||
/// <summary>
|
||||
/// Sample Rate
|
||||
/// This usually has to be 44100 for ISoundProvider
|
||||
/// </summary>
|
||||
private int _sampleRate;
|
||||
public int SampleRate
|
||||
{
|
||||
get { return _sampleRate; }
|
||||
set { _sampleRate = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buzzer volume
|
||||
/// Accepts an int 0-100 value
|
||||
/// </summary>
|
||||
private int _volume;
|
||||
public int Volume
|
||||
{
|
||||
get
|
||||
{
|
||||
return VolumeConverterOut(_volume);
|
||||
}
|
||||
set
|
||||
{
|
||||
var newVol = VolumeConverterIn(value);
|
||||
if (newVol != _volume)
|
||||
blip.Clear();
|
||||
_volume = VolumeConverterIn(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The last used volume (used to modify blipbuffer delta values)
|
||||
/// </summary>
|
||||
private int lastVolume;
|
||||
|
||||
/// <summary>
|
||||
/// The number of cpu cycles per frame
|
||||
/// </summary>
|
||||
private long _tStatesPerFrame;
|
||||
|
||||
/// <summary>
|
||||
/// The parent emulated machine
|
||||
/// </summary>
|
||||
private SpectrumBase _machine;
|
||||
|
||||
/// <summary>
|
||||
/// The last pulse
|
||||
/// </summary>
|
||||
private bool LastPulse;
|
||||
|
||||
/// <summary>
|
||||
/// The last T-State (cpu cycle) that the last pulse was received
|
||||
/// </summary>
|
||||
private long LastPulseTState;
|
||||
|
||||
/// <summary>
|
||||
/// Device blipbuffer
|
||||
/// </summary>
|
||||
private readonly BlipBuffer blip = new BlipBuffer(882);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Takes an int 0-100 and returns the relevant short volume to output
|
||||
/// </summary>
|
||||
/// <param name="vol"></param>
|
||||
/// <returns></returns>
|
||||
private int VolumeConverterIn(int vol)
|
||||
{
|
||||
int maxLimit = short.MaxValue / 3;
|
||||
int increment = maxLimit / 100;
|
||||
|
||||
return vol * increment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes an short volume and returns the relevant int value 0-100
|
||||
/// </summary>
|
||||
/// <param name="vol"></param>
|
||||
/// <returns></returns>
|
||||
private int VolumeConverterOut(int shortvol)
|
||||
{
|
||||
int maxLimit = short.MaxValue / 3;
|
||||
int increment = maxLimit / 100;
|
||||
|
||||
if (shortvol > maxLimit)
|
||||
shortvol = maxLimit;
|
||||
|
||||
return shortvol / increment;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction & Initialisation
|
||||
|
||||
public Beeper(SpectrumBase machine)
|
||||
{
|
||||
_machine = machine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialises the beeper
|
||||
/// </summary>
|
||||
public void Init(int sampleRate, int tStatesPerFrame)
|
||||
{
|
||||
blip.SetRates((tStatesPerFrame * 50), sampleRate);
|
||||
_sampleRate = sampleRate;
|
||||
_tStatesPerFrame = tStatesPerFrame;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IBeeperDevice
|
||||
|
||||
/// <summary>
|
||||
/// Processes an incoming pulse value and adds it to the blipbuffer
|
||||
/// </summary>
|
||||
/// <param name="pulse"></param>
|
||||
public void ProcessPulseValue(bool pulse)
|
||||
{
|
||||
if (!_machine._renderSound)
|
||||
return;
|
||||
|
||||
if (LastPulse == pulse)
|
||||
{
|
||||
// no change
|
||||
blip.AddDelta((uint)_machine.CurrentFrameCycle, 0);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (pulse)
|
||||
blip.AddDelta((uint)_machine.CurrentFrameCycle, (short)(_volume));
|
||||
else
|
||||
blip.AddDelta((uint)_machine.CurrentFrameCycle, -(short)(_volume));
|
||||
|
||||
lastVolume = _volume;
|
||||
}
|
||||
|
||||
LastPulse = pulse;
|
||||
}
|
||||
|
||||
|
||||
public void StartFrame()
|
||||
{
|
||||
}
|
||||
|
||||
public void EndFrame()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region ISoundProvider
|
||||
|
||||
public bool CanProvideAsync => false;
|
||||
|
||||
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
||||
|
||||
public void SetSyncMode(SyncSoundMode mode)
|
||||
{
|
||||
if (mode != SyncSoundMode.Sync)
|
||||
throw new InvalidOperationException("Only Sync mode is supported.");
|
||||
}
|
||||
|
||||
public void GetSamplesAsync(short[] samples)
|
||||
{
|
||||
throw new NotSupportedException("Async is not available");
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
blip.Clear();
|
||||
}
|
||||
|
||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||
{
|
||||
blip.EndFrame((uint)_tStatesPerFrame);
|
||||
nsamp = blip.SamplesAvailable();
|
||||
samples = new short[nsamp * 2];
|
||||
blip.ReadSamples(samples, nsamp, true);
|
||||
for (int i = 0; i < nsamp * 2; i += 2)
|
||||
{
|
||||
samples[i + 1] = samples[i];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State Serialization
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("Buzzer");
|
||||
ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame);
|
||||
ser.Sync("_sampleRate", ref _sampleRate);
|
||||
ser.Sync("LastPulse", ref LastPulse);
|
||||
ser.Sync("LastPulseTState", ref LastPulseTState);
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/*
|
||||
/// <summary>
|
||||
/// Represents the piezoelectric buzzer used in the Spectrum to produce sound
|
||||
/// The beeper is controlled by rapidly toggling bit 4 of port &FE
|
||||
|
@ -146,8 +147,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
{
|
||||
_sampleRate = sampleRate;
|
||||
_tStatesPerFrame = tStatesPerFrame;
|
||||
_tStatesPerSample = 79;
|
||||
_samplesPerFrame = (int)_tStatesPerFrame / _tStatesPerSample;
|
||||
_tStatesPerSample = 99; // 79;
|
||||
_samplesPerFrame = 705; // 882; // (int)_tStatesPerFrame / _tStatesPerSample;
|
||||
|
||||
Pulses = new List<Pulse>(1000);
|
||||
}
|
||||
|
@ -297,7 +298,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
}
|
||||
|
||||
samples = stereoBuffer;
|
||||
nsamp = _samplesPerFrame; // soundBufferContains;
|
||||
nsamp = soundBufferContains; // _samplesPerFrame; // soundBufferContains;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -319,4 +320,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
#endregion
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -152,8 +152,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
if (_renderSound)
|
||||
{
|
||||
BuzzerDevice.StartFrame();
|
||||
TapeBuzzer.StartFrame();
|
||||
//BuzzerDevice.StartFrame();
|
||||
//TapeBuzzer.StartFrame();
|
||||
|
||||
if (AYDevice != null)
|
||||
AYDevice.StartFrame();
|
||||
|
@ -183,8 +183,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
if (_renderSound)
|
||||
{
|
||||
BuzzerDevice.EndFrame();
|
||||
TapeBuzzer.EndFrame();
|
||||
//BuzzerDevice.EndFrame();
|
||||
//TapeBuzzer.EndFrame();
|
||||
}
|
||||
|
||||
if (AYDevice != null)
|
||||
|
@ -365,7 +365,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
if (AYDevice != null)
|
||||
{
|
||||
AYDevice.SyncState(ser);
|
||||
((AYChip)AYDevice as AYChip).PanningConfiguration = Spectrum.Settings.AYPanConfig;
|
||||
((AY38912)AYDevice as AY38912).PanningConfiguration = Spectrum.Settings.AYPanConfig;
|
||||
}
|
||||
|
||||
ser.Sync("tapeMediaIndex", ref tapeMediaIndex);
|
||||
|
|
|
@ -552,13 +552,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
public int VsyncNumerator
|
||||
{
|
||||
get { return ClockSpeed; }
|
||||
get { return ClockSpeed * 50; }// ClockSpeed; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public int VsyncDenominator
|
||||
{
|
||||
get { return FrameLength; }
|
||||
get { return ClockSpeed; }//FrameLength; }
|
||||
}
|
||||
|
||||
public int[] GetVideoBuffer()
|
||||
|
|
|
@ -28,13 +28,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
ULADevice = new ULA128(this);
|
||||
|
||||
BuzzerDevice = new Buzzer(this);
|
||||
BuzzerDevice = new Beeper(this);
|
||||
BuzzerDevice.Init(44100, ULADevice.FrameLength);
|
||||
|
||||
TapeBuzzer = new Buzzer(this);
|
||||
TapeBuzzer = new Beeper(this);
|
||||
TapeBuzzer.Init(44100, ULADevice.FrameLength);
|
||||
|
||||
AYDevice = new AYChip(this);
|
||||
AYDevice = new AY38912(this);
|
||||
AYDevice.Init(44100, ULADevice.FrameLength);
|
||||
|
||||
KeyboardDevice = new StandardKeyboard(this);
|
||||
|
|
|
@ -28,13 +28,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
ULADevice = new ULAPlus2a(this);
|
||||
|
||||
BuzzerDevice = new Buzzer(this);
|
||||
BuzzerDevice = new Beeper(this);
|
||||
BuzzerDevice.Init(44100, ULADevice.FrameLength);
|
||||
|
||||
TapeBuzzer = new Buzzer(this);
|
||||
TapeBuzzer = new Beeper(this);
|
||||
TapeBuzzer.Init(44100, ULADevice.FrameLength);
|
||||
|
||||
AYDevice = new AYChip(this);
|
||||
AYDevice = new AY38912(this);
|
||||
AYDevice.Init(44100, ULADevice.FrameLength);
|
||||
|
||||
KeyboardDevice = new StandardKeyboard(this);
|
||||
|
|
|
@ -28,13 +28,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
ULADevice = new ULAPlus3(this);
|
||||
|
||||
BuzzerDevice = new Buzzer(this);
|
||||
BuzzerDevice = new Beeper(this);
|
||||
BuzzerDevice.Init(44100, ULADevice.FrameLength);
|
||||
|
||||
TapeBuzzer = new Buzzer(this);
|
||||
TapeBuzzer = new Beeper(this);
|
||||
TapeBuzzer.Init(44100, ULADevice.FrameLength);
|
||||
|
||||
AYDevice = new AYChip(this);
|
||||
AYDevice = new AY38912(this);
|
||||
AYDevice.Init(44100, ULADevice.FrameLength);
|
||||
|
||||
KeyboardDevice = new StandardKeyboard(this);
|
||||
|
|
|
@ -23,10 +23,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
ULADevice = new ULA48(this);
|
||||
|
||||
BuzzerDevice = new Buzzer(this);
|
||||
BuzzerDevice = new Beeper(this);
|
||||
BuzzerDevice.Init(44100, ULADevice.FrameLength);
|
||||
|
||||
TapeBuzzer = new Buzzer(this);
|
||||
TapeBuzzer = new Beeper(this);
|
||||
TapeBuzzer.Init(44100, ULADevice.FrameLength);
|
||||
|
||||
KeyboardDevice = new StandardKeyboard(this);
|
||||
|
|
|
@ -28,16 +28,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
// restore user settings to devices
|
||||
if (_machine != null && _machine.AYDevice != null)
|
||||
{
|
||||
((AYChip)_machine.AYDevice as AYChip).PanningConfiguration = o.AYPanConfig;
|
||||
((AY38912)_machine.AYDevice as AY38912).PanningConfiguration = o.AYPanConfig;
|
||||
_machine.AYDevice.Volume = o.AYVolume;
|
||||
}
|
||||
if (_machine != null && _machine.BuzzerDevice != null)
|
||||
{
|
||||
((Buzzer)_machine.BuzzerDevice as Buzzer).Volume = o.EarVolume;
|
||||
((Beeper)_machine.BuzzerDevice as Beeper).Volume = o.EarVolume;
|
||||
}
|
||||
if (_machine != null && _machine.TapeBuzzer != null)
|
||||
{
|
||||
((Buzzer)_machine.TapeBuzzer as Buzzer).Volume = o.TapeVolume;
|
||||
((Beeper)_machine.TapeBuzzer as Beeper).Volume = o.TapeVolume;
|
||||
}
|
||||
|
||||
Settings = o;
|
||||
|
@ -56,8 +56,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
{
|
||||
[DisplayName("AY-3-8912 Panning Config")]
|
||||
[Description("Set the PSG panning configuration.\nThe chip has 3 audio channels that can be outputed in different configurations")]
|
||||
[DefaultValue(AYChip.AYPanConfig.ABC)]
|
||||
public AYChip.AYPanConfig AYPanConfig { get; set; }
|
||||
[DefaultValue(AY38912.AYPanConfig.ABC)]
|
||||
public AY38912.AYPanConfig AYPanConfig { get; set; }
|
||||
|
||||
[DisplayName("Core OSD Message Verbosity")]
|
||||
[Description("Full: Display all GUI messages\nMedium: Display only emulator/device generated messages\nNone: Show no messages")]
|
||||
|
|
|
@ -103,23 +103,24 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
SoundMixer.AddSource(_machine.AYDevice);
|
||||
|
||||
// set audio device settings
|
||||
if (_machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AYChip))
|
||||
if (_machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AY38912))
|
||||
{
|
||||
((AYChip)_machine.AYDevice as AYChip).PanningConfiguration = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYPanConfig;
|
||||
((AY38912)_machine.AYDevice as AY38912).PanningConfiguration = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYPanConfig;
|
||||
_machine.AYDevice.Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYVolume;
|
||||
}
|
||||
|
||||
if (_machine.BuzzerDevice != null)
|
||||
{
|
||||
((Buzzer)_machine.BuzzerDevice as Buzzer).Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).EarVolume;
|
||||
((Beeper)_machine.BuzzerDevice as Beeper).Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).EarVolume;
|
||||
}
|
||||
|
||||
if (_machine.TapeBuzzer != null)
|
||||
{
|
||||
((Buzzer)_machine.TapeBuzzer as Buzzer).Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).TapeVolume;
|
||||
((Beeper)_machine.TapeBuzzer as Beeper).Volume = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).TapeVolume;
|
||||
}
|
||||
|
||||
ser.Register<ISoundProvider>(SoundMixer);
|
||||
//ser.Register < ISoundProvider>(((ISoundProvider)_machine.BuzzerDevice));
|
||||
|
||||
|
||||
HardReset();
|
||||
|
|
Loading…
Reference in New Issue