From c17930c40f7b56d4576771ec23d014ad4df1da55 Mon Sep 17 00:00:00 2001 From: CasualPokePlayer <50538166+CasualPokePlayer@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:56:12 -0700 Subject: [PATCH] Do ChannelF timings a bit better; some cleanups --- .../Fairchild/ChannelF/ChannelF.IEmulator.cs | 57 +++++- .../Fairchild/ChannelF/ChannelF.ISettable.cs | 40 +--- .../ChannelF/ChannelF.ISoundProvider.cs | 187 +++++++++--------- .../Fairchild/ChannelF/ChannelF.IStatable.cs | 19 +- .../ChannelF/ChannelF.IVideoProvider.cs | 4 +- .../Consoles/Fairchild/ChannelF/ChannelF.cs | 13 +- .../Consoles/Fairchild/ChannelF/Ports.cs | 4 +- 7 files changed, 170 insertions(+), 154 deletions(-) diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IEmulator.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IEmulator.cs index 681de25e83..fb3c6b9bd6 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IEmulator.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IEmulator.cs @@ -12,32 +12,71 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF public bool DeterministicEmulation { get; set; } - private double cpuFreq; - private double refreshRate => region == RegionType.NTSC ? 60.5307257846 : 50.0801282051; - public int ClockPerFrame; - public int FrameClock = 0; + public int FrameClock; private void CalcClock() { // CPU speeds from https://en.wikipedia.org/wiki/Fairchild_Channel_F + // also https://github.com/mamedev/mame/blob/c8192c898ce7f68c0c0b87e44199f0d3e710439b/src/mame/drivers/channelf.cpp + double cpuFreq, pixelClock; + int pixelClocksPerFrame; if (Region == DisplayType.NTSC) - { - cpuFreq = 1.7897725; + { + // NTSC CPU speed is NTSC Colorburst / 2 + const double NTSC_COLORBURST = 4500000 * 227.5 / 286; + cpuFreq = NTSC_COLORBURST / 2; + // NTSC pixel clock is NTSC Colorburst * 8 / 7 + pixelClock = NTSC_COLORBURST * 8 / 7; + // NTSC refresh rate is (pixelclock * 8 / 7) / (256 * 264) + pixelClocksPerFrame = 256 * 264; + // (aka (1023750000 * 8) / (256 * 264 * 286 * 7) + // reduced to 234375 / 3872 + VsyncNumerator = 234375; + VsyncDenominator = 3872; } else { if (version == ConsoleVersion.ChannelF) { - cpuFreq = 2.0; + // PAL CPU speed is 2MHz + cpuFreq = 2000000; + // PAL pixel clock is 4MHz + pixelClock = 4000000; + // PAL refresh rate is pixelclock / (256 * 312) + pixelClocksPerFrame = 256 * 312; + // reduced to 15625 / 312 + VsyncNumerator = 15625; + VsyncDenominator = 312; } else if (version == ConsoleVersion.ChannelF_II) { - cpuFreq = 1.9704972; + // PAL CPU speed for gen 2 seems to be contested + // various sources seem to say 1.77MHz (i.e. PAL Colorburst * 2 / 5) + // wikipedia used to have such, before changing it to 1.97MHz (i.e. PAL Colorburst * 4 / 9) + // if we assume the pixel clock is double the cpu freq, then 1.97MHz makes more sense (49.3Hz) + // 1.77MHz * 2 would result in 44.3Hz, complete nonsense for PAL + // however, this kind of relationship between the CPU freq and pixel clock is necessarily the case (see NTSC) + // wikipedia's numbers seem to come from a mame contributor (e5frog) editing the page anyways, so it's probably trustworthy + const double PAL_COLORBURST = 15625 * 283.75 + 25; + cpuFreq = PAL_COLORBURST * 4 / 9; + // not entirely sure what the pixel clock for PAL is here + // presumingly, it's just cpuFreq * 2? + pixelClock = PAL_COLORBURST * 8 / 9; + // PAL refresh rate is pixelclock / (256 * 312) + pixelClocksPerFrame = 256 * 312; + // (aka (4433618.75 * 8) / (256 * 312 * 9) + // reduced to 17734475 / 359424 + VsyncNumerator = 17734475; + VsyncDenominator = 359424; + } + else + { + throw new InvalidOperationException(); } } - var c = ((cpuFreq * 1000000) / refreshRate); + var c = cpuFreq * pixelClocksPerFrame / pixelClock; ClockPerFrame = (int) c; SetupAudio(); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISettable.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISettable.cs index d0e69e000d..da4b8a3740 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISettable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISettable.cs @@ -5,48 +5,26 @@ using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Consoles.ChannelF { - public partial class ChannelF : ISettable + public partial class ChannelF : ISettable { - internal ChannelFSettings Settings = new ChannelFSettings(); - internal ChannelFSyncSettings SyncSettings = new ChannelFSyncSettings(); + private ChannelFSyncSettings _syncSettings; - public ChannelFSettings GetSettings() - { - return Settings.Clone(); - } + public object GetSettings() + => null; public ChannelFSyncSettings GetSyncSettings() - { - return SyncSettings.Clone(); - } + => _syncSettings.Clone(); - public PutSettingsDirtyBits PutSettings(ChannelFSettings o) - { - Settings = o; - return PutSettingsDirtyBits.None; - } + public PutSettingsDirtyBits PutSettings(object o) + => PutSettingsDirtyBits.None; public PutSettingsDirtyBits PutSyncSettings(ChannelFSyncSettings o) { - bool ret = ChannelFSyncSettings.NeedsReboot(SyncSettings, o); - SyncSettings = o; + var ret = ChannelFSyncSettings.NeedsReboot(_syncSettings, o); + _syncSettings = o; return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None; } - [CoreSettings] - public class ChannelFSettings - { - public ChannelFSettings Clone() - { - return (ChannelFSettings)MemberwiseClone(); - } - - public ChannelFSettings() - { - SettingsUtil.SetDefaultValues(this); - } - } - [CoreSettings] public class ChannelFSyncSettings { diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs index 1c4a2637a1..852b746476 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs @@ -6,40 +6,48 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF /// Sound related functions /// public partial class ChannelF : ISoundProvider - { - private int tone = 0; - private short[] sampleBuffer; - private int[] toneBuffer; - private readonly double sampleRate = 44100; - private readonly double decay = 0.998; - private readonly int rampUpTime = 10; - private int samplesPerFrame; - private double cyclesPerSample; - private double amplitude = 0; - private int rampCounter = 0; + { + private const double SAMPLE_RATE = 44100; + private const double DECAY = 0.998; + private const int RAMP_UP_TIME = 10; + + private int _tone; + private short[] _sampleBuffer; + private double[] _filteredSampleBuffer; + private int[] _toneBuffer; + private int _samplesPerFrame; + private double _cyclesPerSample; + private double _amplitude; + private int _rampCounter; + + private int _currTone; + private int _samplePosition; + + private short[] _finalSampleBuffer = [ ]; private void SetupAudio() { - samplesPerFrame = (int)(sampleRate / refreshRate); - cyclesPerSample = ClockPerFrame / (double)samplesPerFrame; - sampleBuffer = new short[samplesPerFrame]; - toneBuffer = new int[samplesPerFrame]; + _samplesPerFrame = (int)(SAMPLE_RATE * VsyncDenominator / VsyncNumerator); + // TODO: more precise audio clocking + _cyclesPerSample = ClockPerFrame / (double)_samplesPerFrame; + _sampleBuffer = new short[_samplesPerFrame]; + _filteredSampleBuffer = new double[_samplesPerFrame]; + _toneBuffer = new int[_samplesPerFrame]; } private void AudioChange() { - var currSample = (int)(FrameClock / cyclesPerSample); + var currSample = (int)(FrameClock / _cyclesPerSample); - while (currSample < samplesPerFrame) + while (currSample < _samplesPerFrame) { - toneBuffer[currSample++] = tone; + _toneBuffer[currSample++] = _tone; } } - private int GetWaveSample(double time, int tone) + private static int GetWaveSample(double time, int tone) { - int t = 0; - + var t = 0; switch (tone) { case 0: @@ -63,79 +71,71 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF return t; } - - private int GetSineWaveSample(double time, double freq) +#if false + private static int GetSineWaveSample(double time, double freq) { - double sTime = time / sampleRate; - double sAngle = 2.0 * Math.PI * sTime * freq; + var sTime = time / SAMPLE_RATE; + var sAngle = 2.0 * Math.PI * sTime * freq; return (int)(Math.Sin(sAngle) * 0x8000); - } - - private int GetSquareWaveSample(double time, double freq) + } +#endif + private static int GetSquareWaveSample(double time, double freq) { - double sTime = time / sampleRate; - double period = 1.0 / freq; - double halfPeriod = period / 2.0; - double currentTimeInPeriod = sTime % period; + var sTime = time / SAMPLE_RATE; + var period = 1.0 / freq; + var halfPeriod = period / 2.0; + var currentTimeInPeriod = sTime % period; return currentTimeInPeriod < halfPeriod ? 32766 : -32766; } - private void ApplyLowPassFilter(short[] samples, double cutoffFrequency) + private static void ApplyLowPassFilter(short[] samples, double cutoffFrequency) { if (samples == null || samples.Length == 0) return; - double rc = 1.0 / (cutoffFrequency * 2 * Math.PI); - double dt = 1.0 / sampleRate; - double alpha = dt / (rc + dt); + var rc = 1.0 / (cutoffFrequency * 2 * Math.PI); + const double dt = 1.0 / SAMPLE_RATE; + var alpha = dt / (rc + dt); - short previousSample = samples[0]; - for (int i = 1; i < samples.Length; i++) + var previousSample = samples[0]; + for (var i = 1; i < samples.Length; i++) { samples[i] = (short)(previousSample + alpha * (samples[i] - previousSample)); previousSample = samples[i]; } } - private void ApplyBassBoostFilter(short[] samples, double boostAmount, double cutoffFrequency) + private static void ApplyBassBoostFilter(short[] samples, double[] filteredSamples, double boostAmount, double cutoffFrequency) { if (samples == null || samples.Length == 0) return; - double A = Math.Pow(10, boostAmount / 40); - double omega = 2 * Math.PI * cutoffFrequency / sampleRate; - double sn = Math.Sin(omega); - double cs = Math.Cos(omega); - double alpha = sn / 2 * Math.Sqrt((A + 1 / A) * (1 / 0.707 - 1) + 2); - double beta = 2 * Math.Sqrt(A) * alpha; + var A = Math.Pow(10, boostAmount / 40); + var omega = 2 * Math.PI * cutoffFrequency / SAMPLE_RATE; + var sn = Math.Sin(omega); + var cs = Math.Cos(omega); + var alpha = sn / 2 * Math.Sqrt((A + 1 / A) * (1 / 0.707 - 1) + 2); + var beta = 2 * Math.Sqrt(A) * alpha; - double b0 = A * ((A + 1) - (A - 1) * cs + beta); - double b1 = 2 * A * ((A - 1) - (A + 1) * cs); - double b2 = A * ((A + 1) - (A - 1) * cs - beta); - double a0 = (A + 1) + (A - 1) * cs + beta; - double a1 = -2 * ((A - 1) + (A + 1) * cs); - double a2 = (A + 1) + (A - 1) * cs - beta; + var b0 = A * ((A + 1) - (A - 1) * cs + beta); + var b1 = 2 * A * ((A - 1) - (A + 1) * cs); + var b2 = A * ((A + 1) - (A - 1) * cs - beta); + var a0 = (A + 1) + (A - 1) * cs + beta; + var a1 = -2 * ((A - 1) + (A + 1) * cs); + var a2 = (A + 1) + (A - 1) * cs - beta; - double[] filteredSamples = new double[samples.Length]; filteredSamples[0] = samples[0]; filteredSamples[1] = samples[1]; - for (int i = 2; i < samples.Length; i++) + for (var i = 2; i < samples.Length; i++) { filteredSamples[i] = (b0 / a0) * samples[i] + (b1 / a0) * samples[i - 1] + (b2 / a0) * samples[i - 2] - (a1 / a0) * filteredSamples[i - 1] - (a2 / a0) * filteredSamples[i - 2]; } - for (int i = 0; i < samples.Length; i++) + for (var i = 0; i < samples.Length; i++) { - samples[i] = Clamp((short)filteredSamples[i], short.MinValue, short.MaxValue); - } - - short Clamp(short value, short min, short max) - { - if (value < min) return min; - if (value > max) return max; - return value; + samples[i] = (short)Math.Min(Math.Max(filteredSamples[i], short.MinValue), short.MaxValue); } } @@ -156,76 +156,77 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF public void DiscardSamples() { - sampleBuffer = new short[samplesPerFrame]; + Array.Clear(_sampleBuffer, 0, _sampleBuffer.Length); // pre-populate the tonebuffer with the last active tone from the frame (will be overwritten if and when a tone change happens) - int lastTone = toneBuffer[^1]; - for (int i = 0; i < toneBuffer.Length; i++) - toneBuffer[i] = lastTone; + var lastTone = _toneBuffer[^1]; + for (var i = 0; i < _toneBuffer.Length; i++) + _toneBuffer[i] = lastTone; } - int currTone = 0; - int samplePosition = 0; - public void GetSamplesSync(out short[] samples, out int nsamp) { // process tone buffer - for (int t = 0; t < toneBuffer.Length; t++) + for (var t = 0; t < _toneBuffer.Length; t++) { - var tValue = toneBuffer[t]; + var tValue = _toneBuffer[t]; - if (currTone != tValue) + if (_currTone != tValue) { // tone is changing if (tValue == 0) { // immediate silence - currTone = tValue; - sampleBuffer[t] = 0; + _currTone = tValue; + _sampleBuffer[t] = 0; } else { // tone change - amplitude = 1; - samplePosition = 0; - rampCounter = rampUpTime; - currTone = tValue; + _amplitude = 1; + _samplePosition = 0; + _rampCounter = RAMP_UP_TIME; + _currTone = tValue; - if (rampCounter <= 0) - sampleBuffer[t] = (short)((GetWaveSample(samplePosition++, currTone) * amplitude) / 30); + if (_rampCounter <= 0) + _sampleBuffer[t] = (short)((GetWaveSample(_samplePosition++, _currTone) * _amplitude) / 30); } } - else if (currTone > 0) + else if (_currTone > 0) { // tone is continuing - if (rampCounter <= 0) + if (_rampCounter <= 0) { - amplitude *= decay; - samplePosition %= samplesPerFrame; - sampleBuffer[t] = (short)((GetWaveSample(samplePosition++, currTone) * amplitude) / 30); + _amplitude *= DECAY; + _samplePosition %= _samplesPerFrame; + _sampleBuffer[t] = (short)((GetWaveSample(_samplePosition++, _currTone) * _amplitude) / 30); } else { - rampCounter--; + _rampCounter--; } } else { // explicit silence - sampleBuffer[t] = 0; + _sampleBuffer[t] = 0; } } - ApplyBassBoostFilter(sampleBuffer, 4.0, 600); - ApplyLowPassFilter(sampleBuffer, 10000); + ApplyBassBoostFilter(_sampleBuffer, _filteredSampleBuffer, 4.0, 600); + ApplyLowPassFilter(_sampleBuffer, 10000); - nsamp = sampleBuffer.Length; - samples = new short[nsamp * 2]; + nsamp = _sampleBuffer.Length; + if (_finalSampleBuffer.Length < nsamp * 2) + { + _finalSampleBuffer = new short[nsamp * 2]; + } + samples = _finalSampleBuffer; for (int i = 0, s = 0; i < nsamp; i++, s += 2) { - samples[s] = (short)(sampleBuffer[i] * 10); - samples[s + 1] = (short)(sampleBuffer[i] * 10); + samples[s] = (short)(_sampleBuffer[i] * 10); + samples[s + 1] = (short)(_sampleBuffer[i] * 10); } DiscardSamples(); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs index 37c9b63ee3..90138540f7 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs @@ -18,15 +18,16 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF ser.Sync(nameof(_isLag), ref _isLag); ser.Sync(nameof(_lagCount), ref _lagCount); - ser.Sync(nameof(tone), ref tone); - ser.Sync(nameof(sampleBuffer), ref sampleBuffer, false); - ser.Sync(nameof(toneBuffer), ref toneBuffer, false); - ser.Sync(nameof(samplesPerFrame), ref samplesPerFrame); - ser.Sync(nameof(cyclesPerSample), ref cyclesPerSample); - ser.Sync(nameof(amplitude), ref amplitude); - ser.Sync(nameof(rampCounter), ref rampCounter); - ser.Sync(nameof(currTone), ref currTone); - ser.Sync(nameof(samplePosition), ref samplePosition); + ser.Sync(nameof(_tone), ref _tone); + ser.Sync(nameof(_sampleBuffer), ref _sampleBuffer, false); + ser.Sync(nameof(_filteredSampleBuffer), ref _filteredSampleBuffer, false); + ser.Sync(nameof(_toneBuffer), ref _toneBuffer, false); + ser.Sync(nameof(_samplesPerFrame), ref _samplesPerFrame); + ser.Sync(nameof(_cyclesPerSample), ref _cyclesPerSample); + ser.Sync(nameof(_amplitude), ref _amplitude); + ser.Sync(nameof(_rampCounter), ref _rampCounter); + ser.Sync(nameof(_currTone), ref _currTone); + ser.Sync(nameof(_samplePosition), ref _samplePosition); ser.Sync(nameof(StateConsole), ref StateConsole, false); ser.Sync(nameof(StateRight), ref StateRight, false); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IVideoProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IVideoProvider.cs index c572fde69c..935c7c28d3 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IVideoProvider.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IVideoProvider.cs @@ -99,8 +99,8 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF public int BufferWidth => 256 - lBorder - rBorder; public int BufferHeight => (64 * scanlineRepeats) - tBorder - bBorder; public int BackgroundColor => Colors.ARGB(0xFF, 0xFF, 0xFF); - public int VsyncNumerator => (int)refreshRate; - public int VsyncDenominator => 1; + public int VsyncNumerator { get; private set; } + public int VsyncDenominator { get; private set; } public int[] TrimOutputBuffer(int[] buff, int leftTrim, int topTrim, int rightTrim, int bottomTrim) { diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.cs index 7480173018..f15b103b96 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.cs @@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF public partial class ChannelF : IDriveLight { [CoreConstructor(VSystemID.Raw.ChannelF)] - public ChannelF(CoreLoadParameters lp) + public ChannelF(CoreLoadParameters lp) { var ser = new BasicServiceProvider(this); ServiceProvider = ser; @@ -18,13 +18,11 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF _gameInfo = lp.Roms.Select(r => r.Game).ToList(); _files = lp.Roms.Select(r => r.RomData).ToList(); - Settings = lp.Settings ?? new ChannelFSettings(); - SyncSettings = lp.SyncSettings ?? new ChannelFSyncSettings(); + _syncSettings = lp.SyncSettings ?? new ChannelFSyncSettings(); + region = _syncSettings.Region; + version = _syncSettings.Version; - region = SyncSettings.Region; - version = SyncSettings.Version; - - MemoryCallbacks = new MemoryCallbackSystem(new[] { "System Bus" }); + MemoryCallbacks = new MemoryCallbackSystem([ "System Bus" ]); ControllerDefinition = ChannelFControllerDefinition; @@ -40,7 +38,6 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF } Cartridge = VesCartBase.Configure(_gameInfo[0], _files[0]); - CPU = new F3850 { diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs index b161c4660b..76548cb2ff 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs @@ -126,9 +126,9 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF OutputLatch[addr] = value; latch_y = (value | 0xC0) ^ 0xFF; var audio = ((value) >> 6) & 0x03; - if (audio != tone) + if (audio != _tone) { - tone = audio; + _tone = audio; AudioChange(); }