SMS: Refactor sound to not use AsyncSound
This commit is contained in:
parent
f987ede70d
commit
9f75222284
|
@ -1437,9 +1437,6 @@
|
|||
<Compile Include="Consoles\Sega\SMS\SMS.ISettable.cs">
|
||||
<DependentUpon>SMS.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Sega\SMS\SMS.ISoundProvider.cs">
|
||||
<DependentUpon>SMS.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Consoles\Sega\SMS\SMS.IStatable.cs">
|
||||
<DependentUpon>SMS.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
@ -1545,6 +1542,7 @@
|
|||
</Compile>
|
||||
<Compile Include="SideBySideVideo.cs" />
|
||||
<Compile Include="Sound\DualSyncSound.cs" />
|
||||
<Compile Include="Sound\SN76489sms.cs" />
|
||||
<Compile Include="Waterbox\CustomSaverammer.cs" />
|
||||
<Compile Include="Waterbox\ElfRunner.cs" />
|
||||
<Compile Include="FileID.cs" />
|
||||
|
@ -1576,7 +1574,6 @@
|
|||
<Compile Include="Sound\HuC6280PSG.cs" />
|
||||
<Compile Include="Sound\IAsyncSoundProvider.cs" />
|
||||
<Compile Include="Sound\MMC5Audio.cs" />
|
||||
<Compile Include="Sound\SN76489.cs" />
|
||||
<Compile Include="Sound\SoundMixer.cs" />
|
||||
<Compile Include="Sound\Sunsoft5BAudio.cs" />
|
||||
<Compile Include="Sound\VRC6Alt.cs" />
|
||||
|
|
|
@ -79,7 +79,7 @@ namespace BizHawk.Emulation.Cores.Sega.GGHawkLink
|
|||
R.Vdp.ProcessLineInterrupt();
|
||||
|
||||
// 512 cycles per line
|
||||
for (int j = 0; j < 512; j++)
|
||||
for (int j = 0; j < 228; j++)
|
||||
{
|
||||
L.Cpu.ExecuteOne();
|
||||
R.Cpu.ExecuteOne();
|
||||
|
@ -89,6 +89,43 @@ namespace BizHawk.Emulation.Cores.Sega.GGHawkLink
|
|||
* Linking code goes here
|
||||
*
|
||||
*/
|
||||
|
||||
L.PSG.generate_sound(1);
|
||||
R.PSG.generate_sound(1);
|
||||
|
||||
int s_L = L.PSG.current_sample_L;
|
||||
int s_R = L.PSG.current_sample_R;
|
||||
|
||||
if (s_L != L.old_s_L)
|
||||
{
|
||||
L.blip_L.AddDelta(L.sampleclock, s_L - L.old_s_L);
|
||||
L.old_s_L = s_L;
|
||||
}
|
||||
|
||||
if (s_R != L.old_s_R)
|
||||
{
|
||||
L.blip_R.AddDelta(L.sampleclock, s_R - L.old_s_R);
|
||||
L.old_s_R = s_R;
|
||||
}
|
||||
|
||||
L.sampleclock++;
|
||||
|
||||
s_L = R.PSG.current_sample_L;
|
||||
s_R = R.PSG.current_sample_R;
|
||||
|
||||
if (s_L != R.old_s_L)
|
||||
{
|
||||
R.blip_L.AddDelta(R.sampleclock, s_L - R.old_s_L);
|
||||
R.old_s_L = s_L;
|
||||
}
|
||||
|
||||
if (s_R != R.old_s_R)
|
||||
{
|
||||
R.blip_R.AddDelta(R.sampleclock, s_R - R.old_s_R);
|
||||
R.old_s_R = s_R;
|
||||
}
|
||||
|
||||
R.sampleclock++;
|
||||
}
|
||||
|
||||
if (S == scanlinesPerFrame - 1)
|
||||
|
@ -208,8 +245,8 @@ namespace BizHawk.Emulation.Cores.Sega.GGHawkLink
|
|||
int nsamp_L = 735;
|
||||
int nsamp_R = 735;
|
||||
|
||||
L.PSG.GetSamples(temp_samp_L);
|
||||
R.PSG.GetSamples(temp_samp_R);
|
||||
L.GetSamplesSync(out temp_samp_L, out nsamp_L);
|
||||
R.GetSamplesSync(out temp_samp_R, out nsamp_L);
|
||||
|
||||
if (linkSettings.AudioSet == GGLinkSettings.AudioSrc.Left)
|
||||
{
|
||||
|
@ -235,8 +272,8 @@ namespace BizHawk.Emulation.Cores.Sega.GGHawkLink
|
|||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
L.PSG.DiscardSamples();
|
||||
R.PSG.DiscardSamples();
|
||||
L.DiscardSamples();
|
||||
R.DiscardSamples();
|
||||
}
|
||||
|
||||
private void GetSamples(short[] samples)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
using BizHawk.Emulation.Common;
|
||||
using System;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
||||
{
|
||||
public partial class SMS : IEmulator
|
||||
public partial class SMS : IEmulator, ISoundProvider
|
||||
{
|
||||
public IEmulatorServiceProvider ServiceProvider { get; }
|
||||
|
||||
|
@ -42,11 +43,10 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
_controller = controller;
|
||||
_lagged = true;
|
||||
_frame++;
|
||||
PSG.BeginFrame(Cpu.TotalExecutedCycles);
|
||||
|
||||
if (!IsGameGear)
|
||||
{
|
||||
PSG.StereoPanning = Settings.ForceStereoSeparation ? ForceStereoByte : (byte)0xFF;
|
||||
PSG.Set_Panning(Settings.ForceStereoSeparation ? ForceStereoByte : (byte)0xFF);
|
||||
}
|
||||
|
||||
if (Tracer.Enabled)
|
||||
|
@ -69,14 +69,51 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
|
||||
if (IsGame3D && Settings.Fix3D)
|
||||
{
|
||||
Vdp.ExecFrame((Frame & 1) == 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Vdp.ExecFrame(render);
|
||||
render = ((Frame & 1) == 0) & render;
|
||||
}
|
||||
|
||||
int scanlinesPerFrame = Vdp.DisplayType == DisplayType.NTSC ? 262 : 313;
|
||||
Vdp.SpriteLimit = Settings.SpriteLimit;
|
||||
for (int i = 0; i < scanlinesPerFrame; i++)
|
||||
{
|
||||
Vdp.ScanLine = i;
|
||||
|
||||
Vdp.RenderCurrentScanline(render);
|
||||
|
||||
Vdp.ProcessFrameInterrupt();
|
||||
Vdp.ProcessLineInterrupt();
|
||||
ProcessLineControls();
|
||||
|
||||
for (int j = 0; j < Vdp.IPeriod; j++)
|
||||
{
|
||||
Cpu.ExecuteOne();
|
||||
PSG.generate_sound(1);
|
||||
|
||||
int s_L = PSG.current_sample_L;
|
||||
int s_R = PSG.current_sample_R;
|
||||
|
||||
if (s_L != old_s_L)
|
||||
{
|
||||
blip_L.AddDelta(sampleclock, s_L - old_s_L);
|
||||
old_s_L = s_L;
|
||||
}
|
||||
|
||||
if (s_R != old_s_R)
|
||||
{
|
||||
blip_R.AddDelta(sampleclock, s_R - old_s_R);
|
||||
old_s_R = s_R;
|
||||
}
|
||||
|
||||
sampleclock++;
|
||||
}
|
||||
|
||||
if (Vdp.ScanLine == scanlinesPerFrame - 1)
|
||||
{
|
||||
Vdp.ProcessGGScreen();
|
||||
Vdp.ProcessOverscan();
|
||||
}
|
||||
}
|
||||
|
||||
PSG.EndFrame(Cpu.TotalExecutedCycles);
|
||||
if (_lagged)
|
||||
{
|
||||
_lagCount++;
|
||||
|
@ -95,7 +132,6 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
{
|
||||
_lagged = true;
|
||||
_frame++;
|
||||
PSG.BeginFrame(Cpu.TotalExecutedCycles);
|
||||
|
||||
if (!IsGameGear && IsGameGear_C)
|
||||
{
|
||||
|
@ -106,7 +142,6 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
// Used for GG linked play
|
||||
public void FrameAdvancePost()
|
||||
{
|
||||
PSG.EndFrame(Cpu.TotalExecutedCycles);
|
||||
if (_lagged)
|
||||
{
|
||||
_lagCount++;
|
||||
|
@ -135,6 +170,98 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
if (blip_L != null)
|
||||
{
|
||||
blip_L.Dispose();
|
||||
blip_L = null;
|
||||
}
|
||||
|
||||
if (blip_R != null)
|
||||
{
|
||||
blip_R.Dispose();
|
||||
blip_R = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Audio
|
||||
|
||||
public BlipBuffer blip_L = new BlipBuffer(4096);
|
||||
public BlipBuffer blip_R = new BlipBuffer(4096);
|
||||
const int blipbuffsize = 4096;
|
||||
|
||||
public uint sampleclock;
|
||||
public int old_s_L = 0;
|
||||
public int old_s_R = 0;
|
||||
|
||||
public bool CanProvideAsync { get { return false; } }
|
||||
|
||||
public void SetSyncMode(SyncSoundMode mode)
|
||||
{
|
||||
if (mode != SyncSoundMode.Sync)
|
||||
{
|
||||
throw new NotSupportedException("Only sync mode is supported");
|
||||
}
|
||||
}
|
||||
|
||||
public void GetSamplesAsync(short[] samples)
|
||||
{
|
||||
throw new NotSupportedException("Async not supported");
|
||||
}
|
||||
|
||||
public SyncSoundMode SyncMode
|
||||
{
|
||||
get { return SyncSoundMode.Sync; }
|
||||
}
|
||||
|
||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||
{
|
||||
if (!disablePSG)
|
||||
{
|
||||
blip_L.EndFrame(sampleclock);
|
||||
blip_R.EndFrame(sampleclock);
|
||||
|
||||
nsamp = Math.Max(Math.Max(blip_L.SamplesAvailable(), blip_R.SamplesAvailable()), 1);
|
||||
samples = new short[nsamp * 2];
|
||||
|
||||
blip_L.ReadSamplesLeft(samples, nsamp);
|
||||
blip_R.ReadSamplesRight(samples, nsamp);
|
||||
|
||||
ApplyYMAudio(samples);
|
||||
}
|
||||
else
|
||||
{
|
||||
nsamp = 735;
|
||||
samples = new short[nsamp * 2];
|
||||
ApplyYMAudio(samples);
|
||||
}
|
||||
|
||||
sampleclock = 0;
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
blip_L.Clear();
|
||||
blip_R.Clear();
|
||||
sampleclock = 0;
|
||||
}
|
||||
|
||||
public void ApplyYMAudio(short[] samples)
|
||||
{
|
||||
if (HasYM2413)
|
||||
{
|
||||
short[] fmsamples = new short[samples.Length];
|
||||
YM2413.GetSamples(fmsamples);
|
||||
//naive mixing. need to study more
|
||||
int len = samples.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
short fmsamp = fmsamples[i];
|
||||
samples[i] = (short)(samples[i] + fmsamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
using BizHawk.Emulation.Cores.Components;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
||||
{
|
||||
// Sound refactor TODO: Implement ISoundProvider without the need for FakeSyncSound
|
||||
public partial class SMS
|
||||
{
|
||||
private FakeSyncSound _fakeSyncSound;
|
||||
private IAsyncSoundProvider ActiveSoundProvider;
|
||||
private SoundMixer SoundMixer;
|
||||
}
|
||||
}
|
|
@ -75,6 +75,10 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
ser.Sync("cntr_rd_1", ref cntr_rd_1);
|
||||
ser.Sync("cntr_rd_2", ref cntr_rd_2);
|
||||
ser.Sync("stand_alone", ref stand_alone);
|
||||
ser.Sync("disablePSG", ref disablePSG);
|
||||
ser.Sync("sampleclock", ref sampleclock);
|
||||
ser.Sync("old_s_L", ref old_s_L);
|
||||
ser.Sync("old_s_R", ref old_s_R);
|
||||
|
||||
if (SaveRAM != null)
|
||||
{
|
||||
|
|
|
@ -104,17 +104,18 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
|
||||
Vdp = new VDP(this, Cpu, IsGameGear ? VdpMode.GameGear : VdpMode.SMS, Region);
|
||||
(ServiceProvider as BasicServiceProvider).Register<IVideoProvider>(Vdp);
|
||||
PSG = new SN76489();
|
||||
PSG = new SN76489sms();
|
||||
YM2413 = new YM2413();
|
||||
SoundMixer = new SoundMixer(YM2413, PSG);
|
||||
//SoundMixer = new SoundMixer(YM2413, PSG);
|
||||
if (HasYM2413 && game["WhenFMDisablePSG"])
|
||||
{
|
||||
SoundMixer.DisableSource(PSG);
|
||||
disablePSG = true;
|
||||
}
|
||||
|
||||
ActiveSoundProvider = HasYM2413 ? (IAsyncSoundProvider)SoundMixer : PSG;
|
||||
_fakeSyncSound = new FakeSyncSound(ActiveSoundProvider, 735);
|
||||
(ServiceProvider as BasicServiceProvider).Register<ISoundProvider>(_fakeSyncSound);
|
||||
blip_L.SetRates(3579545, 44100);
|
||||
blip_R.SetRates(3579545, 44100);
|
||||
|
||||
(ServiceProvider as BasicServiceProvider).Register<ISoundProvider>(this);
|
||||
|
||||
SystemRam = new byte[0x2000];
|
||||
|
||||
|
@ -144,7 +145,7 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
ForceStereoByte = byte.Parse(game.OptionValue("StereoByte"));
|
||||
}
|
||||
|
||||
PSG.StereoPanning = ForceStereoByte;
|
||||
PSG.Set_Panning(ForceStereoByte);
|
||||
}
|
||||
|
||||
if (SyncSettings.AllowOverlock && game["OverclockSafe"])
|
||||
|
@ -230,13 +231,14 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
public Z80A Cpu;
|
||||
public byte[] SystemRam;
|
||||
public VDP Vdp;
|
||||
public SN76489 PSG;
|
||||
public SN76489sms PSG;
|
||||
private YM2413 YM2413;
|
||||
public bool IsGameGear { get; set; }
|
||||
public bool IsGameGear_C { get; set; }
|
||||
public bool IsSG1000 { get; set; }
|
||||
|
||||
private bool HasYM2413 = false;
|
||||
private bool disablePSG = false;
|
||||
private bool PortDEEnabled = false;
|
||||
private IController _controller = NullController.Instance;
|
||||
|
||||
|
@ -387,13 +389,13 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
case 0x01: Port01 = value; break;
|
||||
case 0x02: Port02 = value; break;
|
||||
case 0x05: Port05 = value; break;
|
||||
case 0x06: PSG.StereoPanning = value; break;
|
||||
case 0x06: PSG.Set_Panning(value); break;
|
||||
case 0x3E: Port3E = value; break;
|
||||
case 0x3F: Port3F = value; break;
|
||||
}
|
||||
}
|
||||
else if (port < 0x80) // PSG
|
||||
PSG.WritePsgData(value, Cpu.TotalExecutedCycles);
|
||||
PSG.WriteReg(value);
|
||||
else if (port < 0xC0) // VDP
|
||||
{
|
||||
if ((port & 1) == 0)
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
|
||||
SMS Sms;
|
||||
VdpMode mode;
|
||||
DisplayType DisplayType = DisplayType.NTSC;
|
||||
public DisplayType DisplayType = DisplayType.NTSC;
|
||||
Z80A Cpu;
|
||||
|
||||
public bool SpriteLimit;
|
||||
|
@ -368,31 +368,6 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem
|
|||
lineIntLinesRemaining = Registers[0x0A];
|
||||
}
|
||||
|
||||
public void ExecFrame(bool render)
|
||||
{
|
||||
int scanlinesPerFrame = DisplayType == DisplayType.NTSC ? 262 : 313;
|
||||
SpriteLimit = Sms.Settings.SpriteLimit;
|
||||
for (ScanLine = 0; ScanLine < scanlinesPerFrame; ScanLine++)
|
||||
{
|
||||
RenderCurrentScanline(render);
|
||||
|
||||
ProcessFrameInterrupt();
|
||||
ProcessLineInterrupt();
|
||||
Sms.ProcessLineControls();
|
||||
|
||||
for (int j = 0; j < IPeriod; j++)
|
||||
{
|
||||
Cpu.ExecuteOne();
|
||||
}
|
||||
|
||||
if (ScanLine == scanlinesPerFrame - 1)
|
||||
{
|
||||
ProcessGGScreen();
|
||||
ProcessOverscan();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RenderCurrentScanline(bool render)
|
||||
{
|
||||
// only mode 4 supports frameskip. deal with it
|
||||
|
|
|
@ -1,258 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Components
|
||||
{
|
||||
public sealed class SN76489 : IMixedSoundProvider
|
||||
{
|
||||
public sealed class Channel
|
||||
{
|
||||
public ushort Frequency;
|
||||
public byte Volume;
|
||||
public short[] Wave;
|
||||
public bool Noise;
|
||||
public byte NoiseType;
|
||||
public float WaveOffset;
|
||||
public bool Left = true;
|
||||
public bool Right = true;
|
||||
|
||||
const int SampleRate = 44100;
|
||||
private static readonly byte[] LogScale = { 0, 10, 13, 16, 20, 26, 32, 40, 51, 64, 81, 102, 128, 161, 203, 255 };
|
||||
|
||||
public void Mix(short[] samples, long start, long len, int maxVolume)
|
||||
{
|
||||
if (Volume == 0) return;
|
||||
|
||||
float adjustedWaveLengthInSamples = SampleRate / (Noise ? (Frequency / (float)Wave.Length) : Frequency);
|
||||
float moveThroughWaveRate = Wave.Length / adjustedWaveLengthInSamples;
|
||||
|
||||
long end = start + len;
|
||||
for (long i = start; i < end; )
|
||||
{
|
||||
short value = Wave[(int)WaveOffset];
|
||||
|
||||
samples[i++] += (short)(Left ? (value / 4 * LogScale[Volume] / 0xFF * maxVolume / short.MaxValue) : 0);
|
||||
samples[i++] += (short)(Right ? (value / 4 * LogScale[Volume] / 0xFF * maxVolume / short.MaxValue) : 0);
|
||||
WaveOffset += moveThroughWaveRate;
|
||||
if (WaveOffset >= Wave.Length)
|
||||
WaveOffset %= Wave.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Channel[] Channels = new Channel[4];
|
||||
public byte PsgLatch;
|
||||
|
||||
private readonly Queue<QueuedCommand> commands = new Queue<QueuedCommand>(256);
|
||||
long frameStartTime, frameStopTime;
|
||||
|
||||
const int PsgBase = 111861;
|
||||
|
||||
public SN76489()
|
||||
{
|
||||
MaxVolume = short.MaxValue * 2 / 3;
|
||||
Waves.InitWaves();
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
Channels[i] = new Channel();
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
Channels[i].Wave = Waves.ImperfectSquareWave;
|
||||
break;
|
||||
case 3:
|
||||
Channels[i].Wave = Waves.NoiseWave;
|
||||
Channels[i].Noise = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
PsgLatch = 0;
|
||||
foreach (var channel in Channels)
|
||||
{
|
||||
channel.Frequency = 0;
|
||||
channel.Volume = 0;
|
||||
channel.NoiseType = 0;
|
||||
channel.WaveOffset = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginFrame(long cycles)
|
||||
{
|
||||
while (commands.Count > 0)
|
||||
{
|
||||
var cmd = commands.Dequeue();
|
||||
WritePsgDataImmediate(cmd.Value);
|
||||
}
|
||||
frameStartTime = cycles;
|
||||
}
|
||||
|
||||
public void EndFrame(long cycles)
|
||||
{
|
||||
frameStopTime = cycles;
|
||||
}
|
||||
|
||||
public void WritePsgData(byte value, long cycles)
|
||||
{
|
||||
commands.Enqueue(new QueuedCommand { Value = value, Time = cycles - frameStartTime });
|
||||
}
|
||||
|
||||
void UpdateNoiseType(int value)
|
||||
{
|
||||
Channels[3].NoiseType = (byte)(value & 0x07);
|
||||
switch (Channels[3].NoiseType & 3)
|
||||
{
|
||||
case 0: Channels[3].Frequency = PsgBase / 16; break;
|
||||
case 1: Channels[3].Frequency = PsgBase / 32; break;
|
||||
case 2: Channels[3].Frequency = PsgBase / 64; break;
|
||||
case 3: Channels[3].Frequency = Channels[2].Frequency; break;
|
||||
}
|
||||
var newWave = (value & 4) == 0 ? Waves.PeriodicWave16 : Waves.NoiseWave;
|
||||
if (newWave != Channels[3].Wave)
|
||||
{
|
||||
Channels[3].Wave = newWave;
|
||||
Channels[3].WaveOffset = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
void WritePsgDataImmediate(byte value)
|
||||
{
|
||||
switch (value & 0xF0)
|
||||
{
|
||||
case 0x80:
|
||||
case 0xA0:
|
||||
case 0xC0:
|
||||
PsgLatch = value;
|
||||
break;
|
||||
case 0xE0:
|
||||
PsgLatch = value;
|
||||
UpdateNoiseType(value);
|
||||
break;
|
||||
case 0x90:
|
||||
Channels[0].Volume = (byte)(~value & 15);
|
||||
PsgLatch = value;
|
||||
break;
|
||||
case 0xB0:
|
||||
Channels[1].Volume = (byte)(~value & 15);
|
||||
PsgLatch = value;
|
||||
break;
|
||||
case 0xD0:
|
||||
Channels[2].Volume = (byte)(~value & 15);
|
||||
PsgLatch = value;
|
||||
break;
|
||||
case 0xF0:
|
||||
Channels[3].Volume = (byte)(~value & 15);
|
||||
PsgLatch = value;
|
||||
break;
|
||||
default:
|
||||
byte channel = (byte)((PsgLatch & 0x60) >> 5);
|
||||
if ((PsgLatch & 16) == 0) // Tone latched
|
||||
{
|
||||
int f = PsgBase / (((value & 0x03F) * 16) + (PsgLatch & 0x0F) + 1);
|
||||
if (f > 15000)
|
||||
f = 0; // upper bound of playable frequency
|
||||
Channels[channel].Frequency = (ushort)f;
|
||||
if ((Channels[3].NoiseType & 3) == 3 && channel == 2)
|
||||
Channels[3].Frequency = (ushort)f;
|
||||
}
|
||||
else
|
||||
{ // volume latched
|
||||
Channels[channel].Volume = (byte)(~value & 15);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
byte stereoPanning = 0xFF;
|
||||
public byte StereoPanning
|
||||
{
|
||||
get
|
||||
{
|
||||
byte value = 0;
|
||||
if (Channels[0].Left) value |= 0x10;
|
||||
if (Channels[0].Right) value |= 0x01;
|
||||
if (Channels[1].Left) value |= 0x20;
|
||||
if (Channels[1].Right) value |= 0x02;
|
||||
if (Channels[2].Left) value |= 0x40;
|
||||
if (Channels[2].Right) value |= 0x04;
|
||||
if (Channels[3].Left) value |= 0x80;
|
||||
if (Channels[3].Right) value |= 0x08;
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
Channels[0].Left = (value & 0x10) != 0;
|
||||
Channels[0].Right = (value & 0x01) != 0;
|
||||
Channels[1].Left = (value & 0x20) != 0;
|
||||
Channels[1].Right = (value & 0x02) != 0;
|
||||
Channels[2].Left = (value & 0x40) != 0;
|
||||
Channels[2].Right = (value & 0x04) != 0;
|
||||
Channels[3].Left = (value & 0x80) != 0;
|
||||
Channels[3].Right = (value & 0x08) != 0;
|
||||
stereoPanning = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("PSG");
|
||||
ser.Sync("Volume0", ref Channels[0].Volume);
|
||||
ser.Sync("Volume1", ref Channels[1].Volume);
|
||||
ser.Sync("Volume2", ref Channels[2].Volume);
|
||||
ser.Sync("Volume3", ref Channels[3].Volume);
|
||||
ser.Sync("Freq0", ref Channels[0].Frequency);
|
||||
ser.Sync("Freq1", ref Channels[1].Frequency);
|
||||
ser.Sync("Freq2", ref Channels[2].Frequency);
|
||||
ser.Sync("Freq3", ref Channels[3].Frequency);
|
||||
ser.Sync("NoiseType", ref Channels[3].NoiseType);
|
||||
ser.Sync("PsgLatch", ref PsgLatch);
|
||||
ser.Sync("Panning", ref stereoPanning);
|
||||
ser.EndSection();
|
||||
|
||||
if (ser.IsReader)
|
||||
{
|
||||
StereoPanning = stereoPanning;
|
||||
UpdateNoiseType(Channels[3].NoiseType);
|
||||
}
|
||||
}
|
||||
|
||||
public int MaxVolume { get; set; }
|
||||
public void DiscardSamples() { commands.Clear(); }
|
||||
public void GetSamples(short[] samples)
|
||||
{
|
||||
long elapsedCycles = frameStopTime - frameStartTime;
|
||||
if (elapsedCycles == 0)
|
||||
elapsedCycles = 1; // hey it's better than diving by zero
|
||||
|
||||
long start = 0;
|
||||
while (commands.Count > 0)
|
||||
{
|
||||
var cmd = commands.Dequeue();
|
||||
long pos = ((cmd.Time * samples.Length) / elapsedCycles) & ~1;
|
||||
GetSamplesImmediate(samples, start, pos - start);
|
||||
start = pos;
|
||||
WritePsgDataImmediate(cmd.Value);
|
||||
}
|
||||
GetSamplesImmediate(samples, start, samples.Length - start);
|
||||
}
|
||||
|
||||
public void GetSamplesImmediate(short[] samples, long start, long len)
|
||||
{
|
||||
for (long i = 0; i < 4; i++)
|
||||
Channels[i].Mix(samples, start, len, MaxVolume);
|
||||
}
|
||||
|
||||
class QueuedCommand
|
||||
{
|
||||
public byte Value;
|
||||
public long Time;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
using System;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Components
|
||||
{
|
||||
public sealed class SN76489sms
|
||||
{
|
||||
public int current_sample_L;
|
||||
public int current_sample_R;
|
||||
|
||||
public SN76489sms()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
public byte[] Chan_vol = new byte[4];
|
||||
public ushort[] Chan_tone = new ushort[4];
|
||||
|
||||
public int chan_sel;
|
||||
public bool vol_tone;
|
||||
public bool noise_type;
|
||||
public int noise_rate;
|
||||
public bool noise_bit;
|
||||
|
||||
public bool A_L, B_L, C_L, noise_L;
|
||||
public bool A_R, B_R, C_R, noise_R;
|
||||
|
||||
private int psg_clock;
|
||||
|
||||
private int clock_A, clock_B, clock_C;
|
||||
|
||||
private bool A_up, B_up, C_up;
|
||||
|
||||
private int noise_clock;
|
||||
private int noise;
|
||||
|
||||
public byte stereo_panning;
|
||||
private static readonly byte[] LogScale = { 255, 203, 161, 128, 102, 86, 64, 51, 40, 32, 26, 20, 16, 13, 10, 0 };
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
clock_A = clock_B = clock_C = 0x1000;
|
||||
noise_clock = 0x10;
|
||||
chan_sel = 0;
|
||||
|
||||
// reset the shift register
|
||||
noise = 0x40000;
|
||||
|
||||
Chan_vol[0] = 0xF;
|
||||
Chan_vol[1] = 0xF;
|
||||
Chan_vol[2] = 0xF;
|
||||
Chan_vol[3] = 0xF;
|
||||
|
||||
Set_Panning(0xFF);
|
||||
}
|
||||
|
||||
public void Set_Panning(byte value)
|
||||
{
|
||||
A_L = (value & 0x10) != 0;
|
||||
A_R = (value & 0x01) != 0;
|
||||
B_L = (value & 0x20) != 0;
|
||||
B_R = (value & 0x02) != 0;
|
||||
C_L = (value & 0x40) != 0;
|
||||
C_R = (value & 0x04) != 0;
|
||||
noise_L = (value & 0x80) != 0;
|
||||
noise_R = (value & 0x08) != 0;
|
||||
|
||||
stereo_panning = value;
|
||||
}
|
||||
|
||||
public void SyncState(Serializer ser)
|
||||
{
|
||||
ser.BeginSection("SN76489");
|
||||
|
||||
ser.Sync("Chan_vol", ref Chan_vol, false);
|
||||
ser.Sync("Chan_tone", ref Chan_tone, false);
|
||||
|
||||
ser.Sync("Chan_sel", ref chan_sel);
|
||||
ser.Sync("vol_tone", ref vol_tone);
|
||||
ser.Sync("noise_type", ref noise_type);
|
||||
ser.Sync("noise_rate", ref noise_rate);
|
||||
|
||||
ser.Sync("Clock_A", ref clock_A);
|
||||
ser.Sync("Clock_B", ref clock_B);
|
||||
ser.Sync("Clock_C", ref clock_C);
|
||||
ser.Sync("noise_clock", ref noise_clock);
|
||||
ser.Sync("noise_bit", ref noise_bit);
|
||||
|
||||
ser.Sync("psg_clock", ref psg_clock);
|
||||
|
||||
ser.Sync("A_up", ref A_up);
|
||||
ser.Sync("B_up", ref B_up);
|
||||
ser.Sync("C_up", ref C_up);
|
||||
ser.Sync("noise", ref noise);
|
||||
|
||||
ser.Sync("A_L", ref A_L);
|
||||
ser.Sync("B_L", ref B_L);
|
||||
ser.Sync("C_L", ref C_L);
|
||||
ser.Sync("noise_L", ref noise_L);
|
||||
ser.Sync("A_L", ref A_R);
|
||||
ser.Sync("B_L", ref B_R);
|
||||
ser.Sync("C_L", ref C_R);
|
||||
ser.Sync("noise_L", ref noise_R);
|
||||
|
||||
ser.Sync("current_sample_L", ref current_sample_L);
|
||||
ser.Sync("current_sample_R", ref current_sample_R);
|
||||
ser.Sync("stereo_panning", ref stereo_panning);
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
public byte ReadReg()
|
||||
{
|
||||
// not used, reading not allowed, just return 0xFF
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
public void WriteReg(byte value)
|
||||
{
|
||||
// if bit 7 is set, change the latch, otherwise modify the currently latched register
|
||||
if (value.Bit(7))
|
||||
{
|
||||
chan_sel = (value >> 5) & 3;
|
||||
vol_tone = value.Bit(4);
|
||||
|
||||
if (vol_tone)
|
||||
{
|
||||
Chan_vol[chan_sel] = (byte)(value & 0xF);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (chan_sel < 3)
|
||||
{
|
||||
Chan_tone[chan_sel] &= 0x3F0;
|
||||
Chan_tone[chan_sel] |= (ushort)(value & 0xF);
|
||||
}
|
||||
else
|
||||
{
|
||||
noise_type = value.Bit(2);
|
||||
noise_rate = value & 3;
|
||||
|
||||
// reset the shift register
|
||||
noise = 0x40000;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vol_tone)
|
||||
{
|
||||
Chan_vol[chan_sel] = (byte)(value & 0xF);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (chan_sel < 3)
|
||||
{
|
||||
Chan_tone[chan_sel] &= 0xF;
|
||||
Chan_tone[chan_sel] |= (ushort)((value & 0x3F) << 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
noise_type = value.Bit(2);
|
||||
noise_rate = value & 3;
|
||||
|
||||
// reset the shift register
|
||||
noise = 0x40000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void generate_sound(int cycles_to_do)
|
||||
{
|
||||
// there are 16 cpu cycles for every psg cycle
|
||||
for (int i = 0; i < cycles_to_do; i++)
|
||||
{
|
||||
psg_clock++;
|
||||
|
||||
if (psg_clock == 16)
|
||||
{
|
||||
psg_clock = 0;
|
||||
|
||||
clock_A--;
|
||||
clock_B--;
|
||||
clock_C--;
|
||||
noise_clock--;
|
||||
|
||||
// clock noise
|
||||
if (noise_clock == 0)
|
||||
{
|
||||
noise_bit = noise.Bit(0);
|
||||
if (noise_type)
|
||||
{
|
||||
int bit = (noise & 1) ^ ((noise >> 1) & 1);
|
||||
noise = noise >> 1;
|
||||
noise |= bit << 14;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
int bit = noise & 1;
|
||||
noise = noise >> 1;
|
||||
noise |= bit << 14;
|
||||
}
|
||||
|
||||
if (noise_rate == 0)
|
||||
{
|
||||
noise_clock = 0x10;
|
||||
}
|
||||
else if (noise_rate == 1)
|
||||
{
|
||||
noise_clock = 0x20;
|
||||
}
|
||||
else if (noise_rate == 2)
|
||||
{
|
||||
noise_clock = 0x40;
|
||||
}
|
||||
else
|
||||
{
|
||||
noise_clock = Chan_tone[2] + 1;
|
||||
}
|
||||
|
||||
noise_clock *= 2;
|
||||
}
|
||||
|
||||
if (clock_A == 0)
|
||||
{
|
||||
A_up = !A_up;
|
||||
clock_A = Chan_tone[0] + 1;
|
||||
}
|
||||
|
||||
if (clock_B == 0)
|
||||
{
|
||||
B_up = !B_up;
|
||||
clock_B = Chan_tone[1] + 1;
|
||||
}
|
||||
|
||||
if (clock_C == 0)
|
||||
{
|
||||
C_up = !C_up;
|
||||
clock_C = Chan_tone[2] + 1;
|
||||
}
|
||||
|
||||
// now calculate the volume of each channel and add them together
|
||||
// the magic number 42 is to make the volume comparable to the MSG volume
|
||||
int v_L;
|
||||
int v_R;
|
||||
|
||||
v_L = (A_L ? (A_up ? LogScale[Chan_vol[0]] * 42 : 0) : 0);
|
||||
|
||||
v_L += (B_L ? (B_up ? LogScale[Chan_vol[1]] * 42 : 0) : 0);
|
||||
|
||||
v_L += (C_L ? (C_up ? LogScale[Chan_vol[2]] * 42 : 0) : 0);
|
||||
|
||||
v_L += (noise_L ? (noise_bit ? LogScale[Chan_vol[3]] * 42 : 0) : 0);
|
||||
|
||||
v_R = (A_R ? (A_up ? LogScale[Chan_vol[0]] * 42 : 0) : 0);
|
||||
|
||||
v_R += (B_R ? (B_up ? LogScale[Chan_vol[1]] * 42 : 0) : 0);
|
||||
|
||||
v_R += (C_R ? (C_up ? LogScale[Chan_vol[2]] * 42 : 0) : 0);
|
||||
|
||||
v_R += (noise_R ? (noise_bit ? LogScale[Chan_vol[3]] * 42 : 0) : 0);
|
||||
|
||||
current_sample_L = v_L;
|
||||
|
||||
current_sample_R = v_R;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue