In /emu_65c02, add --unfringe switch
authorNick Downing <nick@ndcode.org>
Sun, 11 Aug 2024 02:30:03 +0000 (12:30 +1000)
committerNick Downing <nick@mary.local>
Sun, 22 Sep 2024 03:51:39 +0000 (13:51 +1000)
emu_65c02/emu_65c02.c
emu_65c02/unfringe.py [new file with mode: 0755]
shape/shape_mono_to_color.py

index abcfddc..cc9a95f 100644 (file)
@@ -26,7 +26,8 @@
 // includes cool down sequence
 #define PADDED_WIDTH (APPLE_WIDTH + 3)
 #define PADDED_HEIGHT APPLE_HEIGHT
-#define PADDED_WORDS ((PADDED_WIDTH + 31) >> 5)
+// note: extra 2 pixels for unfringe
+#define PADDED_WORDS ((PADDED_WIDTH + 33) >> 5)
 
 #define WINDOW_X_SCALE 2
 #define WINDOW_Y_SCALE 4
@@ -153,6 +154,26 @@ uint32_t palette[0x10] = {
 // can make this green or amber to be more realistic
 uint32_t mono_palette[2] = {0xff000000, 0xffffffff};
 
+// see unfringe.py
+uint8_t decode[0x80] = {
+  0x00, 0x00, 0x00, 0x00, 0x44, 0x44, 0x66, 0x00,
+  0x08, 0x08, 0x2a, 0x08, 0x4c, 0x4c, 0x6e, 0x7f,
+  0x11, 0x11, 0x11, 0x33, 0x55, 0x55, 0x66, 0x77,
+  0x19, 0x19, 0x19, 0x19, 0x5d, 0x5d, 0x7f, 0x7f,
+  0x00, 0x22, 0x22, 0x22, 0x44, 0x55, 0x66, 0x66,
+  0x2a, 0x2a, 0x2a, 0x2a, 0x4c, 0x2a, 0x6e, 0x6e,
+  0x33, 0x33, 0x33, 0x33, 0x33, 0x55, 0x77, 0x77,
+  0x3b, 0x19, 0x3b, 0x3b, 0x7f, 0x5d, 0x7f, 0x7f,
+  0x00, 0x00, 0x22, 0x00, 0x44, 0x44, 0x66, 0x44,
+  0x08, 0x08, 0x2a, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c,
+  0x11, 0x11, 0x55, 0x33, 0x55, 0x55, 0x55, 0x55,
+  0x19, 0x19, 0x2a, 0x3b, 0x5d, 0x5d, 0x5d, 0x7f,
+  0x00, 0x00, 0x22, 0x22, 0x66, 0x66, 0x66, 0x66,
+  0x08, 0x19, 0x2a, 0x2a, 0x4c, 0x6e, 0x6e, 0x6e,
+  0x00, 0x11, 0x33, 0x33, 0x77, 0x55, 0x77, 0x77,
+  0x7f, 0x19, 0x3b, 0x3b, 0x7f, 0x7f, 0x7f, 0x7f,
+};
+
 struct cpu_65c02 cpu;
 bool jam_flag;
 
@@ -1186,6 +1207,7 @@ void termios_atexit(void) {
 
 int main(int argc, char **argv) {
   int argn = 1;
+  bool unfringe = false;
   char *cg_rom_file = NULL;
   char *video_rom_file = NULL;
 #if APPLE_IIE
@@ -1201,10 +1223,12 @@ int main(int argc, char **argv) {
 #endif
   while (argn < argc) {
     if (strcmp(argv[argn], "--help") == 0) {
-      printf("usage: %s [--cg-rom=file.bin] [--video-rom=file.bin] [--NN-rom=file.bin ...] [program.obj[,aNNNN] ...] [-- child-executable [child-argument ...]]\n", argv[0]);
+      printf("usage: %s [--unfringe] [--cg-rom=file.bin] [--video-rom=file.bin] [--NN-rom=file.bin ...] [program.obj[,aNNNN] ...] [-- child-executable [child-argument ...]]\n", argv[0]);
       exit(EXIT_FAILURE);
     }
-    if (memcmp(argv[argn], "--cg-rom=", 9) == 0)
+    if (strcmp(argv[argn], "--unfringe") == 0)
+      unfringe = true;
+    else if (memcmp(argv[argn], "--cg-rom=", 9) == 0)
       cg_rom_file = argv[argn] + 9;
     else if (memcmp(argv[argn], "--video_rom=", 12) == 0)
       video_rom_file = argv[argn] + 12;
@@ -1854,6 +1878,19 @@ int main(int argc, char **argv) {
             }
           }
         }
+        else if (unfringe) {
+          // hires or gr (colour burst), interpreted
+          int k = (buf[0] & 3) << 5, x = 0, y = i * WINDOW_Y_SCALE;
+          for (int j = 2; j < PADDED_WIDTH + 2; ++j) {
+            k = (k >> 1) | (((buf[j >> 5] >> (j & 0x1f)) & 1) << 6);
+            uint32_t v = palette[(decode[k] >> ((2 - j) & 3)) & 0xf];
+            for (int l = 0; l < WINDOW_X_SCALE; ++l) {
+              for (int m = 0; m < WINDOW_Y_SCALE; ++m)
+                frame[y + m][x] = v;
+              ++x;
+            }
+          }
+        }
         else {
           // hires or gr (colour burst)
           int k = 0, x = 0, y = i * WINDOW_Y_SCALE;
diff --git a/emu_65c02/unfringe.py b/emu_65c02/unfringe.py
new file mode 100755 (executable)
index 0000000..54e64ba
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+
+# create a lookup table to decode Apple (NTSC) video without fringes
+
+import sys
+
+# decoder uses a majority voting scheme with tie breaking by parity
+def parity(nibble):
+  nibble = (nibble & 3) ^ (nibble >> 2)
+  nibble = (nibble & 1) ^ (nibble >> 1)
+  return nibble
+
+decode = []
+for i in range(0x80):
+  candidates = [
+    i & 0xf,
+    (i & 0xe) | ((i >> 4) & 1),
+    (i & 0xc) | ((i >> 4) & 3),
+    (i & 8) | ((i >> 4) & 7)
+  ]
+  if candidates[1] == candidates[2]:
+    result = candidates[1]
+  elif candidates[0] == candidates[1] and candidates[2] != candidates[3]:
+    result = candidates[0]
+  elif candidates[0] != candidates[1] and candidates[2] == candidates[3]:
+    result = candidates[3]
+  elif 0 in candidates:
+    result = 0
+  elif 0xf in candidates:
+    result = 0xf
+  else:
+    # we assume it is a fringed transition from candidate 0 to candidate 3
+    # on the left are more of candidate 0, on the right are more of candidate 3
+    # so to ensure a clean transition, we just have to choose candidate 0 or 3
+    p0 = parity(candidates[0])
+    p3 = parity(candidates[3])
+    if p0 < p3:
+      result = candidates[0]
+    elif p3 < p0:
+      result = candidates[3]
+    else:
+      assert False
+  # actual colour indicated by "result" should be rotated left by (x + n) % 4,
+  # as we don't have a circular rotate, repeat the lower 3 bits for right shift
+  decode.append(result | ((result & 7) << 4))
+
+sys.stdout.write(
+  '''uint8_t decode[0x80] = {{
+{0:s}}};
+'''.format(
+    ''.join(
+      [
+        '  ' + ' '.join([f'0x{j:02x},' for j in decode[i:i + 8]]) + '\n'
+        for i in range(0, 0x80, 8)
+      ]
+    )
+  )
+)
index b798f98..d015d48 100755 (executable)
@@ -53,17 +53,13 @@ for i in range(0x80):
     result = candidates[0]
   elif candidates[0] != candidates[1] and candidates[2] == candidates[3]:
     result = candidates[3]
-  elif candidates[1] == 0 or candidates[1] == 0xf:
-    # exceptional case: 1100001 -- don't treat as transition from 0001 to 0110
-    # exceptional case: 0011110 -- don't treat as transition from 1110 to 1001
-    result = candidates[1]
-  elif candidates[2] == 0 or candidates[2] == 0xf:
-    # exceptional case: 1000011 -- don't treat as transition from 0011 to 0100
-    # exceptional case: 0111100 -- don't treat as transition from 1100 to 1011
-    result = candidates[2]
+  elif 0 in candidates:
+    result = 0
+  elif 0xf in candidates:
+    result = 0xf
   else:
     # we assume it is a fringed transition from candidate 0 to candidate 3
-    # on the left are more of candidate 0, on the right more of candidate 3
+    # on the left are more of candidate 0, on the right are more of candidate 3
     # so to ensure a clean transition, we just have to choose candidate 0 or 3
     p0 = parity(candidates[0])
     p3 = parity(candidates[3])