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 fb3c6b9bd6..8084f7e818 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IEmulator.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IEmulator.cs
@@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
public bool DeterministicEmulation { get; set; }
- public int ClockPerFrame;
+ public int CpuClocksPerFrame;
public int FrameClock;
private void CalcClock()
@@ -23,6 +23,15 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
int pixelClocksPerFrame;
if (Region == DisplayType.NTSC)
{
+ HTotal = 256;
+ HBlankOff = 8;
+ HBlankOn = 212;
+ VTotal = 264;
+ VBlankOff = 16;
+ VBlankOn = 248;
+ ScanlineRepeats = 4;
+ PixelWidth = 2;
+
// NTSC CPU speed is NTSC Colorburst / 2
const double NTSC_COLORBURST = 4500000 * 227.5 / 286;
cpuFreq = NTSC_COLORBURST / 2;
@@ -37,6 +46,15 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
}
else
{
+ HTotal = 256;
+ HBlankOff = 8;
+ HBlankOn = 212;
+ VTotal = 312;
+ VBlankOff = 20;
+ VBlankOn = 310;
+ ScanlineRepeats = 5;
+ PixelWidth = 2;
+
if (version == ConsoleVersion.ChannelF)
{
// PAL CPU speed is 2MHz
@@ -77,7 +95,9 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
}
var c = cpuFreq * pixelClocksPerFrame / pixelClock;
- ClockPerFrame = (int) c;
+ CpuClocksPerFrame = (int) c;
+ PixelClocksPerCpuClock = pixelClock / cpuFreq;
+ PixelClocksPerFrame = pixelClocksPerFrame;
SetupAudio();
}
@@ -98,9 +118,10 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
PollInput();
- while (FrameClock++ < ClockPerFrame)
+ while (FrameClock++ < CpuClocksPerFrame)
{
CPU.ExecuteOne();
+ ClockVideo();
}
FrameClock = 0;
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 852b746476..4384ac22ca 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs
@@ -29,7 +29,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{
_samplesPerFrame = (int)(SAMPLE_RATE * VsyncDenominator / VsyncNumerator);
// TODO: more precise audio clocking
- _cyclesPerSample = ClockPerFrame / (double)_samplesPerFrame;
+ _cyclesPerSample = CpuClocksPerFrame / (double)_samplesPerFrame;
_sampleBuffer = new short[_samplesPerFrame];
_filteredSampleBuffer = new double[_samplesPerFrame];
_toneBuffer = new int[_samplesPerFrame];
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 90138540f7..a41226f139 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs
@@ -8,13 +8,13 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{
ser.BeginSection("ChannelF");
ser.Sync(nameof(VRAM), ref VRAM, false);
- ser.Sync(nameof(latch_colour), ref latch_colour);
- ser.Sync(nameof(latch_x), ref latch_x);
- ser.Sync(nameof(latch_y), ref latch_y);
+ ser.Sync(nameof(_latch_colour), ref _latch_colour);
+ ser.Sync(nameof(_latch_x), ref _latch_x);
+ ser.Sync(nameof(_latch_y), ref _latch_y);
+ ser.Sync(nameof(_pixelClocksRemaining), ref _pixelClocksRemaining);
ser.Sync(nameof(FrameClock), ref FrameClock);
ser.Sync(nameof(_frame), ref _frame);
- ser.Sync(nameof(ClockPerFrame), ref ClockPerFrame);
ser.Sync(nameof(_isLag), ref _isLag);
ser.Sync(nameof(_lagCount), ref _lagCount);
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 935c7c28d3..adf29e2ce6 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IVideoProvider.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IVideoProvider.cs
@@ -1,5 +1,6 @@
using BizHawk.Common;
using BizHawk.Emulation.Common;
+using BizHawk.Emulation.Cores.Nintendo.NES;
namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{
@@ -7,9 +8,10 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
{
///
/// 128x64 pixels - 8192x2bits (2 KB)
- /// For the purposes of this core we will use 8192 bytes and just & 0x03
+ /// For the purposes of this core we will use 8192 bytes and just mask 0x03
+ /// (Also adding an additional 10 rows to the RAM buffer so that it's more aligned with the actual display)
///
- public byte[] VRAM = new byte[(128 * 64)];
+ public byte[] VRAM = new byte[128 * (64 + 10)];
public static readonly int[] FPalette =
@@ -23,8 +25,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
Colors.ARGB(0x4B, 0x3F, 0xF3), // Blue
Colors.ARGB(0xE0, 0xE0, 0xE0), // Gray
Colors.ARGB(0x91, 0xFF, 0xA6), // BGreen
- Colors.ARGB(0xCE, 0xD0, 0xFF), // BBlue
-
+ Colors.ARGB(0xCE, 0xD0, 0xFF), // BBlue
};
public static readonly int[] CMap =
@@ -35,21 +36,147 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
6, 4, 2, 3,
};
- private int latch_colour = 2; //2;
- private int latch_x;
- private int latch_y;
-
- private int scanlineRepeats;
+ private int _latch_colour = 2;
+ private int _latch_x;
+ private int _latch_y;
+
private int[] frameBuffer;
private int[] outputBuffer;
+ private int[] videoBuffer;
+ private double _pixelClockCounter;
+ private double _pixelClocksRemaining;
+
+
+ private int ScanlineRepeats;
+ private int PixelWidth;
+ private int HTotal;
+ private int HBlankOff;
+ private int HBlankOn;
+ private int VTotal;
+ private int VBlankOff;
+ private int VBlankOn;
+ private double PixelClocksPerCpuClock;
+ private double PixelClocksPerFrame;
public void SetupVideo()
{
- scanlineRepeats = Region == DisplayType.NTSC ? 4 : 5;
- frameBuffer = new int[128 * 64];
- outputBuffer = new int[128 * 2 * 64 * scanlineRepeats];
+
+ outputBuffer = new int[(HBlankOn - HBlankOff) * (VBlankOn - VBlankOff)];
+
+ videoBuffer = new int[HTotal * VTotal];
}
+ ///
+ /// Called after every CPU clock
+ ///
+ private void ClockVideo()
+ {
+ while (_pixelClocksRemaining > 1)
+ {
+ var currScanline = (int)(_pixelClockCounter / HTotal);
+ var currPixelInLine = (int)(_pixelClockCounter % HTotal);
+ var currRowInVram = currScanline / ScanlineRepeats;
+ var currColInVram = currPixelInLine / PixelWidth;
+
+ if (currScanline < VBlankOff || currScanline >= VBlankOn)
+ {
+ // vertical flyback
+ }
+ else if (currPixelInLine < HBlankOff || currPixelInLine >= HBlankOn)
+ {
+ // horizontal flyback
+ }
+ else
+ {
+ // active display
+ var p1 = (VRAM[(currRowInVram * 0x80) + 125]) & 0x03;
+ var p2 = (VRAM[(currRowInVram * 0x80) + 126]) & 0x03;
+ var pOffset = ((p2 & 0x02) | (p1 >> 1)) << 2;
+
+ var colourIndex = pOffset + (VRAM[currColInVram | (currRowInVram << 7)] & 0x03);
+ videoBuffer[(currScanline * HTotal) + currPixelInLine] = FPalette[CMap[colourIndex]];
+ }
+
+ _pixelClockCounter++;
+ _pixelClocksRemaining -= 1;
+ }
+
+ _pixelClocksRemaining += PixelClocksPerCpuClock;
+ _pixelClockCounter %= PixelClocksPerFrame;
+ }
+
+ private int HDisplayable => HBlankOn - HBlankOff;
+ private int VDisplayable => VBlankOn - VBlankOff;
+
+ private double HPixelAspectModifier => Region == DisplayType.NTSC ? 1.75 : 1.95; // This is only here because the aspect ratio is off between regions. It could maybe be negated by trimming the number of scanlines on PAL??
+
+ private int[] ClampBuffer(int[] buffer, int originalWidth, int originalHeight, int trimLeft, int trimTop, int trimRight, int trimBottom)
+ {
+ int newWidth = originalWidth - trimLeft - trimRight;
+ int newHeight = originalHeight - trimTop - trimBottom;
+ int[] newBuffer = new int[newWidth * newHeight];
+
+ for (int y = 0; y < newHeight; y++)
+ {
+ for (int x = 0; x < newWidth; x++)
+ {
+ int originalIndex = (y + trimTop) * originalWidth + (x + trimLeft);
+ int newIndex = y * newWidth + x;
+ newBuffer[newIndex] = buffer[originalIndex];
+ }
+ }
+
+ return newBuffer;
+ }
+
+ private static double GetHorizontalModifier(int bufferWidth, int bufferHeight, double targetAspectRatio)
+ {
+ // Calculate the current aspect ratio
+ double currentAspectRatio = (double)bufferWidth / bufferHeight;
+
+ // Calculate the horizontal modifier needed to achieve the target aspect ratio
+ double horizontalModifier = targetAspectRatio / currentAspectRatio;
+
+ return horizontalModifier;
+ }
+
+ private static double GetVerticalModifier(int bufferWidth, int bufferHeight, double targetAspectRatio)
+ {
+ // Calculate the current aspect ratio
+ double currentAspectRatio = (double)bufferWidth / bufferHeight;
+
+ // Calculate the vertical modifier needed to achieve the target aspect ratio
+ double verticalModifier = currentAspectRatio / targetAspectRatio;
+
+ return verticalModifier;
+ }
+
+ public int VirtualWidth => HDisplayable * 2;
+ public int VirtualHeight => (int)(VDisplayable * GetVerticalModifier(HDisplayable, VDisplayable, 4.0/3.0)) * 2;
+ public int BufferWidth => HDisplayable;
+ public int BufferHeight => VDisplayable;
+ public int BackgroundColor => Colors.ARGB(0xFF, 0xFF, 0xFF);
+ public int VsyncNumerator { get; private set; }
+ public int VsyncDenominator { get; private set; }
+
+
+ public int[] GetVideoBuffer()
+ {
+ // https://channelf.se/veswiki/index.php?title=VRAM
+ // 'The emulator MESS uses a fixed 102x58 resolution starting at (4,4) but the safe area for a real system is about 95x58 pixels'
+ // 'Note that the pixel aspect is a lot closer to widescreen (16:9) than standard definition (4:3). On a TV from the 70's or 80's pixels are rectangular, standing up. In widescreen mode they are close to perfect squares'
+ // https://channelf.se/veswiki/index.php?title=Resolution
+ // 'Even though PAL televisions system has more lines vertically, the Channel F displays about the same as on the original NTSC video system'
+ //
+ // Right now we are just trimming based on the HBLANK and VBLANK values (we might need to go further like the other emulators)
+ // VirtualWidth is being used to force the aspect ratio into 4:3
+ // On real hardware it looks like this (so we are close): https://www.youtube.com/watch?v=ZvQA9tiEIuQ
+ return ClampBuffer(videoBuffer, HTotal, VTotal, HBlankOff, VBlankOff, HTotal - HBlankOn, VTotal - VBlankOn);
+ }
+
+ public DisplayType Region => region == RegionType.NTSC ? DisplayType.NTSC : DisplayType.PAL;
+
+ /*
private void BuildFrameFromRAM()
{
for (int r = 0; r < 64; r++)
@@ -67,69 +194,6 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
}
}
}
-
- private void ExpandFrame()
- {
- int initialWidth = 128;
- int initialHeight = 64;
-
- for (int lines = 0; lines < initialHeight; lines++)
- {
- for (int i = 0; i < scanlineRepeats; i++)
- {
- for (int x = 0; x < initialWidth; x++)
- {
- for (int j = 0; j < 2; j++)
- {
- outputBuffer[(lines * scanlineRepeats + i) * initialWidth * 2 + x * 2 + j] = FPalette[frameBuffer[lines * initialWidth + x]];
- }
- }
- }
- }
-
- }
-
- public int lBorder => 8;
- public int rBorder => 52;
- public int tBorder => 8;
- public int bBorder => 4;
-
- public int VirtualWidth => (int)((BufferWidth - lBorder - rBorder) * 2.2);
- public int VirtualHeight => Region == DisplayType.NTSC ? BufferHeight - tBorder - bBorder : (int)((BufferHeight - tBorder - bBorder) * 0.8);
- public int BufferWidth => 256 - lBorder - rBorder;
- public int BufferHeight => (64 * scanlineRepeats) - tBorder - bBorder;
- public int BackgroundColor => Colors.ARGB(0xFF, 0xFF, 0xFF);
- 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)
- {
- int initialWidth = 128 * 2;
- int initialHeight = 64 * scanlineRepeats;
- int newWidth = initialWidth - leftTrim - rightTrim;
- int newHeight = initialHeight - topTrim - bottomTrim;
-
- int[] trimmedBuffer = new int[newWidth * newHeight];
-
- for (int y = 0; y < newHeight; y++)
- {
- for (int x = 0; x < newWidth; x++)
- {
- trimmedBuffer[y * newWidth + x] = buff[(y + topTrim) * initialWidth + (x + leftTrim)];
- }
- }
-
- return trimmedBuffer;
- }
-
-
- public int[] GetVideoBuffer()
- {
- BuildFrameFromRAM();
- ExpandFrame();
- return TrimOutputBuffer(outputBuffer, lBorder, tBorder, rBorder, bBorder);
- }
-
- public DisplayType Region => region == RegionType.NTSC ? DisplayType.NTSC : DisplayType.PAL;
+ */
}
}
diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs
index 76548cb2ff..06dd4c0f9b 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs
@@ -107,24 +107,24 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
// WRT pulse
// pulse clocks the 74195 parallel access shift register which feeds inputs of 2 NAND gates
// writing data to both sets of even and odd VRAM chips (based on the row and column addresses latched into the 7493 ICs)
- VRAM[((latch_y) * 0x80) + latch_x] = (byte)latch_colour;
+ VRAM[((_latch_y) * 0x80) + _latch_x] = (byte)_latch_colour;
}
break;
case 1:
OutputLatch[addr] = value;
- latch_colour = ((value ^ 0xFF) >> 6) & 0x03;
+ _latch_colour = ((value ^ 0xFF) >> 6) & 0x03;
break;
case 4:
OutputLatch[addr] = value;
- latch_x = (value | 0x80) ^ 0xFF;
+ _latch_x = (value | 0x80) ^ 0xFF;
break;
case 5:
OutputLatch[addr] = value;
- latch_y = (value | 0xC0) ^ 0xFF;
+ _latch_y = (value | 0xC0) ^ 0xFF;
var audio = ((value) >> 6) & 0x03;
if (audio != _tone)
{