SMS: Refactor sound to not use AsyncSound

This commit is contained in:
alyosha-tas 2019-02-23 11:31:09 -06:00
parent f987ede70d
commit 9f75222284
9 changed files with 472 additions and 326 deletions

View File

@ -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" />

View File

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

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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)
{

View File

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

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}
}