http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.14.tar.gz
[xscreensaver] / hacks / analogtv.c
diff --git a/hacks/analogtv.c b/hacks/analogtv.c
new file mode 100644 (file)
index 0000000..86761b7
--- /dev/null
@@ -0,0 +1,2138 @@
+/* analogtv, Copyright (c) 2003 Trevor Blackwell <tlb@tlb.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/*
+
+  This is the code for implementing something that looks like a conventional
+  analog TV set. It simulates the following characteristics of standard
+  televisions:
+
+  - Realistic rendering of a composite video signal
+  - Compression & brightening on the right, as the scan gets truncated
+    because of saturation in the flyback transformer
+  - Blooming of the picture dependent on brightness
+  - Overscan, cutting off a few pixels on the left side.
+  - Colored text in mixed graphics/text modes
+
+  It's amazing how much it makes your high-end monitor look like at large
+  late-70s TV. All you need is to put a big "Solid State" logo in curly script
+  on it and you'd be set.
+
+  In DirectColor or TrueColor modes, it generates pixel values
+  directly from RGB values it calculates across each scan line. In
+  PseudoColor mode, it consider each possible pattern of 5 preceding
+  bit values in each possible position modulo 4 and allocates a color
+  for each. A few things, like the brightening on the right side as
+  the horizontal trace slows down, aren't done in PseudoColor.
+
+  I originally wrote it for the Apple ][ emulator, and generalized it
+  here for use with a rewrite of xteevee and possibly others.
+
+  A maxim of technology is that failures reveal underlying mechanism.
+  A good way to learn how something works is to push it to failure.
+  The way it fails will usually tell you a lot about how it works. The
+  corollary for this piece of software is that in order to emulate
+  realistic failures of a TV set, it has to work just like a TV set.
+  So there is lots of DSP-style emulation of analog circuitry for
+  things like color decoding, H and V sync following, and more. In
+  2003, computers are just fast enough to do this at television signal
+  rates. We use a 14 MHz sample rate here, so we can do on the order
+  of a couple hundred instructions per sample and keep a good frame
+  rate.
+
+  Trevor Blackwell <tlb@tlb.org>
+*/
+
+#include <X11/Xutil.h>
+#include <X11/Intrinsic.h>
+#include <assert.h>
+#include "utils.h"
+#include "resources.h"
+#include "analogtv.h"
+#include "yarandom.h"
+#include "grabscreen.h"
+
+/* #define DEBUG 1 */
+
+#ifdef DEBUG
+/* only works on linux + freebsd */
+#include <machine/cpufunc.h>
+
+#define DTIME_DECL u_int64_t dtimes[100]; int n_dtimes
+#define DTIME_START do {n_dtimes=0; dtimes[n_dtimes++]=rdtsc(); } while (0)
+#define DTIME dtimes[n_dtimes++]=rdtsc()
+#define DTIME_SHOW(DIV) \
+do { \
+  double _dtime_div=(DIV); \
+  printf("time/%.1f: ",_dtime_div); \
+  for (i=1; i<n_dtimes; i++) \
+    printf(" %0.9f",(dtimes[i]-dtimes[i-1])* 1e-9 / _dtime_div); \
+  printf("\n"); \
+} while (0)
+
+#else
+
+#define DTIME_DECL
+#define DTIME_START  do { } while (0)
+#define DTIME  do { } while (0)
+#define DTIME_SHOW(DIV)  do { } while (0)
+
+#endif
+
+
+#define FASTRND (fastrnd = fastrnd*1103515245+12345)
+
+static void analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
+                                 int start, int end);
+
+static double puramp(analogtv *it, double tc, double start, double over)
+{
+  double pt=it->powerup-start;
+  double ret;
+  if (pt<0.0) return 0.0;
+  if (pt>900.0 || pt/tc>8.0) return 1.0;
+
+  ret=(1.0-exp(-pt/tc))*over;
+  if (ret>1.0) return 1.0;
+  return ret*ret;
+}
+
+/*
+  There are actual standards for TV signals: NTSC and RS-170A describe the
+  system used in the US and Japan. Europe has slightly different systems, but
+  not different enough to make substantially different screensaver displays.
+  Sadly, the standards bodies don't do anything so useful as publish the spec on
+  the web. Best bets are:
+
+    http://www.ee.washington.edu/conselec/CE/kuhn/ntsc/95x4.htm
+    http://www.ntsc-tv.com/ntsc-index-02.htm
+
+  In DirectColor or TrueColor modes, it generates pixel values directly from RGB
+  values it calculates across each scan line. In PseudoColor mode, it consider
+  each possible pattern of 5 preceding bit values in each possible position
+  modulo 4 and allocates a color for each. A few things, like the brightening on
+  the right side as the horizontal trace slows down, aren't done in PseudoColor.
+
+  I'd like to add a bit of visible retrace, but it conflicts with being able to
+  bitcopy the image when fast scrolling. After another couple of CPU
+  generations, we could probably regenerate the whole image from scratch every
+  time. On a P4 2 GHz it can manage this fine for blinking text, but scrolling
+  looks too slow.
+*/
+
+/* localbyteorder is MSBFirst or LSBFirst */
+static int localbyteorder;
+static const double float_low8_ofs=8388608.0;
+static int float_extraction_works;
+
+typedef union {
+  float f;
+  int i;
+} float_extract_t;
+
+static void
+analogtv_init(void)
+{
+  int i;
+  {
+    unsigned int localbyteorder_loc = (MSBFirst<<24) | (LSBFirst<<0);
+    localbyteorder=*(char *)&localbyteorder_loc;
+  }
+
+  if (1) {
+    float_extract_t fe;
+    int ans;
+
+    float_extraction_works=1;
+    for (i=0; i<256*4; i++) {
+      fe.f=float_low8_ofs+(double)i;
+      ans=fe.i&0x3ff;
+      if (ans != i) {
+#ifdef DEBUG
+        printf("Float extraction failed for %d => %d\n",i,ans);
+#endif
+        float_extraction_works=0;
+        break;
+      }
+    }
+  }
+
+}
+
+void
+analogtv_set_defaults(analogtv *it, char *prefix)
+{
+  char buf[256];
+
+  sprintf(buf,"%sTVTint",prefix);
+  it->tint_control = get_float_resource(buf,"TVTint");
+  sprintf(buf,"%sTVColor",prefix);
+  it->color_control = get_float_resource(buf,"TVColor")/100.0;
+  sprintf(buf,"%sTVBrightness",prefix);
+  it->brightness_control = get_float_resource(buf,"TVBrightness") / 100.0;
+  sprintf(buf,"%sTVContrast",prefix);
+  it->contrast_control = get_float_resource(buf,"TVContrast") / 100.0;
+  it->height_control = 1.0;
+  it->width_control = 1.0;
+  it->squish_control = 0.0;
+  it->powerup=1000.0;
+
+  it->hashnoise_rpm=0;
+  it->hashnoise_on=0;
+  it->hashnoise_enable=1;
+
+  it->horiz_desync=frand(10.0)-5.0;
+  it->squeezebottom=frand(5.0)-1.0;
+
+#ifdef DEBUG
+  printf("analogtv: prefix=%s\n",prefix);
+  printf("  use: shm=%d cmap=%d color=%d\n",
+         it->use_shm,it->use_cmap,it->use_color);
+  printf("  controls: tint=%g color=%g brightness=%g contrast=%g\n",
+         it->tint_control, it->color_control, it->brightness_control,
+         it->contrast_control);
+  printf("  freq_error %g: %g %d\n",
+         it->freq_error, it->freq_error_inc, it->flutter_tint);
+  printf("  desync: %g %d\n",
+         it->horiz_desync, it->flutter_horiz_desync);
+  printf("  hashnoise rpm: %g\n",
+         it->hashnoise_rpm);
+  printf("  vis: %d %d %d\n",
+         it->visclass, it->visbits, it->visdepth);
+  printf("  shift: %d-%d %d-%d %d-%d\n",
+         it->red_invprec,it->red_shift,
+         it->green_invprec,it->green_shift,
+         it->blue_invprec,it->blue_shift);
+  printf("  size: %d %d  %d %d  xrepl=%d\n",
+         it->usewidth, it->useheight,
+         it->screen_xo, it->screen_yo, it->xrepl);
+
+  printf("    ANALOGTV_V=%d\n",ANALOGTV_V);
+  printf("    ANALOGTV_TOP=%d\n",ANALOGTV_TOP);
+  printf("    ANALOGTV_VISLINES=%d\n",ANALOGTV_VISLINES);
+  printf("    ANALOGTV_BOT=%d\n",ANALOGTV_BOT);
+  printf("    ANALOGTV_H=%d\n",ANALOGTV_H);
+  printf("    ANALOGTV_SYNC_START=%d\n",ANALOGTV_SYNC_START);
+  printf("    ANALOGTV_BP_START=%d\n",ANALOGTV_BP_START);
+  printf("    ANALOGTV_CB_START=%d\n",ANALOGTV_CB_START);
+  printf("    ANALOGTV_PIC_START=%d\n",ANALOGTV_PIC_START);
+  printf("    ANALOGTV_PIC_LEN=%d\n",ANALOGTV_PIC_LEN);
+  printf("    ANALOGTV_FP_START=%d\n",ANALOGTV_FP_START);
+  printf("    ANALOGTV_PIC_END=%d\n",ANALOGTV_PIC_END);
+  printf("    ANALOGTV_HASHNOISE_LEN=%d\n",ANALOGTV_HASHNOISE_LEN);
+
+#endif
+
+}
+
+extern Bool mono_p; /* shoot me */
+
+void
+analogtv_free_image(analogtv *it)
+{
+  if (it->image) {
+    if (it->use_shm) {
+#ifdef HAVE_XSHM_EXTENSION
+      destroy_xshm_image(it->dpy, it->image, &it->shm_info);
+#endif
+    } else {
+      XDestroyImage(it->image);
+    }
+    it->image=NULL;
+  }
+}
+
+void
+analogtv_alloc_image(analogtv *it)
+{
+  if (it->use_shm) {
+#ifdef HAVE_XSHM_EXTENSION
+    it->image=create_xshm_image(it->dpy, it->xgwa.visual, it->xgwa.depth, ZPixmap, 0,
+                                &it->shm_info, it->usewidth, it->useheight);
+#endif
+    if (!it->image) it->use_shm=0;
+  }
+  if (!it->image) {
+    it->image = XCreateImage(it->dpy, it->xgwa.visual, it->xgwa.depth, ZPixmap, 0, 0,
+                             it->usewidth, it->useheight, 8, 0);
+    it->image->data = (char *)calloc(it->image->height, it->image->bytes_per_line);
+  }
+}
+
+
+void
+analogtv_configure(analogtv *it)
+{
+  int oldwidth=it->usewidth;
+  int oldheight=it->useheight;
+  int wlim,hlim,ohlim;
+
+  hlim=it->xgwa.height;
+  if (hlim<ANALOGTV_VISLINES) hlim = ANALOGTV_VISLINES;
+
+  wlim = it->xgwa.width;
+  if (wlim<300) wlim = 300;
+
+  /* require 3:4 aspect ratio */
+  if (wlim > hlim*4/3) wlim=hlim*4/3;
+  if (hlim > wlim*3/4) hlim=wlim*3/4;
+
+  /* height must be a multiple of VISLINES */
+  ohlim=hlim;
+  hlim = (hlim/ANALOGTV_VISLINES)*ANALOGTV_VISLINES;
+
+  /* Scale width proportionally */
+  wlim=wlim*hlim/ohlim;
+
+  {
+    FILE *fp=fopen("/tmp/analogtv.size","w");
+    fprintf(fp,"wlim=%d hlim=%d\n", wlim, hlim);
+    fclose(fp);
+  }
+
+  /* Most times this doesn't change */
+  if (wlim != oldwidth || hlim != oldheight) {
+
+    it->usewidth=wlim;
+    it->useheight=hlim;
+
+    it->xrepl=1+it->usewidth/640;
+    if (it->xrepl>2) it->xrepl=2;
+    it->subwidth=it->usewidth/it->xrepl;
+
+    analogtv_free_image(it);
+    analogtv_alloc_image(it);
+  }
+
+  it->screen_xo = (it->xgwa.width-it->usewidth)/2;
+  it->screen_yo = (it->xgwa.height-it->useheight)/2;
+  it->need_clear=1;
+}
+
+void
+analogtv_reconfigure(analogtv *it)
+{
+  XGetWindowAttributes (it->dpy, it->window, &it->xgwa);
+  analogtv_configure(it);
+}
+
+analogtv *
+analogtv_allocate(Display *dpy, Window window)
+{
+  XGCValues gcv;
+  analogtv *it=NULL;
+  int i;
+
+  analogtv_init();
+
+  it=(analogtv *)calloc(1,sizeof(analogtv));
+  it->dpy=dpy;
+  it->window=window;
+
+  it->shrinkpulse=-1;
+
+  it->n_colors=0;
+
+#ifdef HAVE_XSHM_EXTENSION
+  it->use_shm=1;
+#else
+  it->use_shm=0;
+#endif
+
+  XGetWindowAttributes (it->dpy, it->window, &it->xgwa);
+
+  it->screen=it->xgwa.screen;
+  it->colormap=it->xgwa.colormap;
+  it->visclass=it->xgwa.visual->class;
+  it->visbits=it->xgwa.visual->bits_per_rgb;
+  it->visdepth=it->xgwa.depth;
+  if (it->visclass == TrueColor || it->visclass == DirectColor) {
+    if (get_integer_resource ("use_cmap", "Integer")) {
+      it->use_cmap=1;
+    } else {
+      it->use_cmap=0;
+    }
+    it->use_color=!mono_p;
+  }
+  else if (it->visclass == PseudoColor || it->visclass == StaticColor) {
+    it->use_cmap=1;
+    it->use_color=!mono_p;
+  }
+  else {
+    it->use_cmap=1;
+    it->use_color=0;
+  }
+
+  it->red_mask=it->xgwa.visual->red_mask;
+  it->green_mask=it->xgwa.visual->green_mask;
+  it->blue_mask=it->xgwa.visual->blue_mask;
+  it->red_shift=it->red_invprec=-1;
+  it->green_shift=it->green_invprec=-1;
+  it->blue_shift=it->blue_invprec=-1;
+  if (!it->use_cmap) {
+    /* Is there a standard way to do this? Does this handle all cases? */
+    int shift, prec;
+    for (shift=0; shift<32; shift++) {
+      for (prec=1; prec<16 && prec<32-shift; prec++) {
+        unsigned long mask=(0xffffUL>>(16-prec)) << shift;
+        if (it->red_shift<0 && mask==it->red_mask)
+          it->red_shift=shift, it->red_invprec=16-prec;
+        if (it->green_shift<0 && mask==it->green_mask)
+          it->green_shift=shift, it->green_invprec=16-prec;
+        if (it->blue_shift<0 && mask==it->blue_mask)
+          it->blue_shift=shift, it->blue_invprec=16-prec;
+      }
+    }
+    if (it->red_shift<0 || it->green_shift<0 || it->blue_shift<0) {
+      if (0) fprintf(stderr,"Can't figure out color space\n");
+      goto fail;
+    }
+
+    for (i=0; i<ANALOGTV_CV_MAX; i++) {
+      int intensity=pow(i/256.0, 0.8)*65535.0; /* gamma correction */
+      if (intensity>65535) intensity=65535;
+      it->red_values[i]=((intensity>>it->red_invprec)<<it->red_shift);
+      it->green_values[i]=((intensity>>it->green_invprec)<<it->green_shift);
+      it->blue_values[i]=((intensity>>it->blue_invprec)<<it->blue_shift);
+    }
+
+  }
+
+  gcv.background=get_pixel_resource("background", "Background",
+                                    it->dpy, it->colormap);
+
+  it->gc = XCreateGC(it->dpy, it->window, GCBackground, &gcv);
+  XSetWindowBackground(it->dpy, it->window, gcv.background);
+  XClearWindow(dpy,window);
+
+  analogtv_configure(it);
+
+  return it;
+
+ fail:
+  if (it) free(it);
+  return NULL;
+}
+
+void
+analogtv_release(analogtv *it)
+{
+  if (it->image) {
+    if (it->use_shm) {
+#ifdef HAVE_XSHM_EXTENSION
+      destroy_xshm_image(it->dpy, it->image, &it->shm_info);
+#endif
+    } else {
+      XDestroyImage(it->image);
+    }
+    it->image=NULL;
+  }
+  if (it->gc) XFreeGC(it->dpy, it->gc);
+  it->gc=NULL;
+  if (it->n_colors) XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L);
+  it->n_colors=0;
+}
+
+
+/*
+  First generate the I and Q reference signals, which we'll multiply
+  the input signal by to accomplish the demodulation. Normally they
+  are shifted 33 degrees from the colorburst. I think this was convenient
+  for inductor-capacitor-vacuum tube implementation.
+
+  The tint control, FWIW, just adds a phase shift to the chroma signal,
+  and the color control controls the amplitude.
+
+  In text modes (colormode==0) the system disabled the color burst, and no
+  color was detected by the monitor.
+
+  freq_error gives a mismatch between the built-in oscillator and the
+  TV's colorbust. Some II Plus machines seemed to occasionally get
+  instability problems -- the crystal oscillator was a single
+  transistor if I remember correctly -- and the frequency would vary
+  enough that the tint would change across the width of the screen.
+  The left side would be in correct tint because it had just gotten
+  resynchronized with the color burst.
+
+  If we're using a colormap, set it up.
+*/
+int
+analogtv_set_demod(analogtv *it)
+{
+  int y_levels=10,i_levels=5,q_levels=5;
+
+  /*
+    In principle, we might be able to figure out how to adjust the
+    color map frame-by-frame to get some nice color bummage. But I'm
+    terrified of changing the color map because we'll get flashing.
+
+    I can hardly believe we still have to deal with colormaps. They're
+    like having NEAR PTRs: an enormous hassle for the programmer just
+    to save on memory. They should have been deprecated by 1995 or
+    so. */
+
+ cmap_again:
+  if (it->use_cmap && !it->n_colors) {
+
+    if (it->n_colors) {
+      XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L);
+      it->n_colors=0;
+    }
+
+    {
+      int yli,qli,ili;
+      for (yli=0; yli<y_levels; yli++) {
+        for (ili=0; ili<i_levels; ili++) {
+          for (qli=0; qli<q_levels; qli++) {
+            double interpy,interpi,interpq;
+            double levelmult=700.0;
+            int r,g,b;
+            XColor col;
+
+            interpy=100.0 * ((double)yli/y_levels);
+            interpi=50.0 * (((double)ili-(0.5*i_levels))/(double)i_levels);
+            interpq=50.0 * (((double)qli-(0.5*q_levels))/(double)q_levels);
+
+            r=(int)((interpy + 1.04*interpi + 0.624*interpq)*levelmult);
+            g=(int)((interpy - 0.276*interpi - 0.639*interpq)*levelmult);
+            b=(int)((interpy - 1.105*interpi + 1.729*interpq)*levelmult);
+            if (r<0) r=0;
+            if (r>65535) r=65535;
+            if (g<0) g=0;
+            if (g>65535) g=65535;
+            if (b<0) b=0;
+            if (b>65535) b=65535;
+
+#ifdef DEBUG
+            printf("%0.2f %0.2f %0.2f => %02x%02x%02x\n",
+                   interpy, interpi, interpq,
+                   r/256,g/256,b/256);
+#endif
+
+            col.red=r;
+            col.green=g;
+            col.blue=b;
+            col.pixel=0;
+            if (!XAllocColor(it->dpy, it->colormap, &col)) {
+              if (q_levels > y_levels*4/12)
+                q_levels--;
+              else if (i_levels > y_levels*5/12)
+                i_levels--;
+              else
+                y_levels--;
+
+              if (y_levels<2)
+                return -1;
+              goto cmap_again;
+            }
+            it->colors[it->n_colors++]=col.pixel;
+          }
+        }
+      }
+
+      it->cmap_y_levels=y_levels;
+      it->cmap_i_levels=i_levels;
+      it->cmap_q_levels=q_levels;
+    }
+  }
+
+  return 0;
+}
+
+#if 0
+unsigned int
+analogtv_line_signature(analogtv_input *input, int lineno)
+{
+  int i;
+  char *origsignal=&input->signal[(lineno+input->vsync)
+                                  %ANALOGTV_V][input->line_hsync[lineno]];
+  unsigned int hash=0;
+
+  /* probably lame */
+  for (i=0; i<ANALOGTV_PIC_LEN; i++) {
+    int c=origsignal[i];
+    hash = hash + (hash<<17) + c;
+  }
+
+  hash += input->line_hsync[lineno];
+  hash ^= hash >> 2;
+  /*
+  hash += input->hashnoise_times[lineno];
+  hash ^= hash >> 2;
+  */
+
+  return hash;
+}
+#endif
+
+
+/* Here we model the analog circuitry of an NTSC television.
+   Basically, it splits the signal into 3 signals: Y, I and Q. Y
+   corresponds to luminance, and you get it by low-pass filtering the
+   input signal to below 3.57 MHz.
+
+   I and Q are the in-phase and quadrature components of the 3.57 MHz
+   subcarrier. We get them by multiplying by cos(3.57 MHz*t) and
+   sin(3.57 MHz*t), and low-pass filtering. Because the eye has less
+   resolution in some colors than others, the I component gets
+   low-pass filtered at 1.5 MHz and the Q at 0.5 MHz. The I component
+   is approximately orange-blue, and Q is roughly purple-green. See
+   http://www.ntsc-tv.com for details.
+
+   We actually do an awful lot to the signal here. I suspect it would
+   make sense to wrap them all up together by calculating impulse
+   response and doing FFT convolutions.
+
+*/
+
+static void
+analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
+                     int start, int end)
+{
+  enum {MAXDELAY=32};
+  int i;
+  double *sp;
+  int phasecorr=(signal-it->rx_signal)&3;
+  struct analogtv_yiq_s *yiq;
+  int colormode;
+  double agclevel=it->agclevel;
+  double brightadd=it->brightness_control*100.0 - ANALOGTV_BLACK_LEVEL;
+  double delay[MAXDELAY+ANALOGTV_PIC_LEN], *dp;
+  double multiq2[4];
+
+  {
+
+    double cb_i=(it->line_cb_phase[lineno][(2+phasecorr)&3]-
+                 it->line_cb_phase[lineno][(0+phasecorr)&3])/16.0;
+    double cb_q=(it->line_cb_phase[lineno][(3+phasecorr)&3]-
+                 it->line_cb_phase[lineno][(1+phasecorr)&3])/16.0;
+
+    colormode = (cb_i * cb_i + cb_q * cb_q) > 2.8;
+
+    if (colormode) {
+      double tint_i = -cos((103 + it->color_control)*3.1415926/180);
+      double tint_q = sin((103 + it->color_control)*3.1415926/180);
+
+      multiq2[0] = (cb_i*tint_i - cb_q*tint_q) * it->color_control;
+      multiq2[1] = (cb_q*tint_i + cb_i*tint_q) * it->color_control;
+      multiq2[2]=-multiq2[0];
+      multiq2[3]=-multiq2[1];
+    }
+  }
+
+#if 0
+  if (lineno==100) {
+    printf("multiq = [%0.3f %0.3f %0.3f %0.3f] ",
+           it->multiq[60],it->multiq[61],it->multiq[62],it->multiq[63]);
+    printf("it->line_cb_phase = [%0.3f %0.3f %0.3f %0.3f]\n",
+           it->line_cb_phase[lineno][0],it->line_cb_phase[lineno][1],
+           it->line_cb_phase[lineno][2],it->line_cb_phase[lineno][3]);
+    printf("multiq2 = [%0.3f %0.3f %0.3f %0.3f]\n",
+           multiq2[0],multiq2[1],multiq2[2],multiq2[3]);
+  }
+#endif
+
+  dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
+  for (i=0; i<5; i++) dp[i]=0.0;
+
+  assert(start>=0);
+  assert(end < ANALOGTV_PIC_LEN+10);
+
+  dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
+  for (i=0; i<24; i++) dp[i]=0.0;
+  for (i=start, yiq=it->yiq+start, sp=signal+start;
+       i<end;
+       i++, dp--, yiq++, sp++) {
+
+    /* Now filter them. These are infinite impulse response filters
+       calculated by the script at
+       http://www-users.cs.york.ac.uk/~fisher/mkfilter. This is
+       fixed-point integer DSP, son. No place for wimps. We do it in
+       integer because you can count on integer being faster on most
+       CPUs. We care about speed because we need to recalculate every
+       time we blink text, and when we spew random bytes into screen
+       memory. This is roughly 16.16 fixed point arithmetic, but we
+       scale some filter values up by a few bits to avoid some nasty
+       precision errors. */
+
+    /* Filter Y with a 4-pole low-pass Butterworth filter at 3.5 MHz
+       with an extra zero at 3.5 MHz, from
+       mkfilter -Bu -Lp -o 4 -a 2.1428571429e-01 0 -Z 2.5e-01 -l
+       Delay about 2 */
+
+    dp[0] = sp[0] * 0.0469904257251935 * agclevel;
+    dp[8] = (+1.0*(dp[6]+dp[0])
+             +4.0*(dp[5]+dp[1])
+             +7.0*(dp[4]+dp[2])
+             +8.0*(dp[3])
+             -0.0176648*dp[12]
+             -0.4860288*dp[10]);
+    yiq->y = dp[8] + brightadd;
+  }
+
+  if (colormode) {
+    dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
+    for (i=0; i<27; i++) dp[i]=0.0;
+
+    for (i=start, yiq=it->yiq+start, sp=signal+start;
+         i<end;
+         i++, dp--, yiq++, sp++) {
+      double sig=*sp;
+
+      /* Filter I and Q with a 3-pole low-pass Butterworth filter at
+         1.5 MHz with an extra zero at 3.5 MHz, from
+         mkfilter -Bu -Lp -o 3 -a 1.0714285714e-01 0 -Z 2.5000000000e-01 -l
+         Delay about 3.
+      */
+
+      dp[0] = sig*multiq2[i&3] * 0.0833333333333;
+      yiq->i=dp[8] = (dp[5] + dp[0]
+                      +3.0*(dp[4] + dp[1])
+                      +4.0*(dp[3] + dp[2])
+                      -0.3333333333 * dp[10]);
+
+      dp[16] = sig*multiq2[(i+3)&3] * 0.0833333333333;
+      yiq->q=dp[24] = (dp[16+5] + dp[16+0]
+                       +3.0*(dp[16+4] + dp[16+1])
+                       +4.0*(dp[16+3] + dp[16+2])
+                       -0.3333333333 * dp[24+2]);
+    }
+  } else {
+    for (i=start, yiq=it->yiq+start; i<end; i++, yiq++) {
+      yiq->i = yiq->q = 0.0;
+    }
+  }
+}
+
+void
+analogtv_setup_teletext(analogtv_input *input)
+{
+  int x,y;
+  int teletext=ANALOGTV_BLACK_LEVEL;
+
+  /* Teletext goes in line 21. But I suspect there are other things
+     in the vertical retrace interval */
+
+  for (y=19; y<22; y++) {
+    for (x=ANALOGTV_PIC_START; x<ANALOGTV_PIC_END; x++) {
+      if ((x&7)==0) {
+        teletext=(random()&1) ? ANALOGTV_WHITE_LEVEL : ANALOGTV_BLACK_LEVEL;
+      }
+      input->signal[y][x]=teletext;
+    }
+  }
+}
+
+void
+analogtv_setup_frame(analogtv *it)
+{
+  int i,x,y;
+
+  it->redraw_all=0;
+
+  if (it->flutter_horiz_desync) {
+    /* Horizontal sync during vertical sync instability. */
+    it->horiz_desync += -0.10*(it->horiz_desync-3.0) +
+      ((int)(random()&0xff)-0x80) *
+      ((int)(random()&0xff)-0x80) *
+      ((int)(random()&0xff)-0x80) * 0.000001;
+  }
+
+  for (i=0; i<ANALOGTV_V; i++) {
+    it->hashnoise_times[i]=0;
+  }
+
+  if (it->hashnoise_enable && !it->hashnoise_on) {
+    if (random()%10000==0) {
+      it->hashnoise_on=1;
+      it->shrinkpulse=random()%ANALOGTV_V;
+    }
+  }
+  if (random()%1000==0) {
+    it->hashnoise_on=0;
+  }
+  if (it->hashnoise_on) {
+    it->hashnoise_rpm += (15000.0 - it->hashnoise_rpm)*0.05 +
+      ((int)(random()%2000)-1000)*0.1;
+  } else {
+    it->hashnoise_rpm -= 100 + 0.01*it->hashnoise_rpm;
+    if (it->hashnoise_rpm<0.0) it->hashnoise_rpm=0.0;
+  }
+  if (it->hashnoise_rpm >= 0.0) {
+    int hni;
+    int hnc=it->hashnoise_counter; /* in 24.8 format */
+
+    /* Convert rpm of a 16-pole motor into dots in 24.8 format */
+    hni = (int)(ANALOGTV_V * ANALOGTV_H * 256.0 /
+                (it->hashnoise_rpm * 16.0 / 60.0 / 60.0));
+
+    while (hnc < (ANALOGTV_V * ANALOGTV_H)<<8) {
+      y=(hnc>>8)/ANALOGTV_H;
+      x=(hnc>>8)%ANALOGTV_H;
+
+      if (x>0 && x<ANALOGTV_H - ANALOGTV_HASHNOISE_LEN) {
+        it->hashnoise_times[y]=x;
+      }
+      hnc += hni + (int)(random()%65536)-32768;
+    }
+    hnc -= (ANALOGTV_V * ANALOGTV_H)<<8;
+  }
+
+  it->agclevel = 1.0/it->rx_signal_level;
+
+
+#ifdef DEBUG2
+  printf("filter: ");
+  for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
+    printf(" %0.3f",it->ghostfir[i]);
+  }
+  printf(" siglevel=%g agc=%g\n", siglevel, it->agclevel);
+#endif
+}
+
+void
+analogtv_setup_sync(analogtv_input *input, int do_cb, int do_ssavi)
+{
+  int i,lineno,vsync;
+  char *sig;
+
+  int synclevel = do_ssavi ? ANALOGTV_WHITE_LEVEL : ANALOGTV_SYNC_LEVEL;
+
+  for (lineno=0; lineno<ANALOGTV_V; lineno++) {
+    vsync=lineno>=3 && lineno<7;
+
+    sig=input->signal[lineno];
+
+    i=ANALOGTV_SYNC_START;
+    if (vsync) {
+      while (i<ANALOGTV_BP_START) sig[i++]=ANALOGTV_BLANK_LEVEL;
+      while (i<ANALOGTV_H) sig[i++]=synclevel;
+    } else {
+      while (i<ANALOGTV_BP_START) sig[i++]=synclevel;
+      while (i<ANALOGTV_PIC_START) sig[i++]=ANALOGTV_BLANK_LEVEL;
+      while (i<ANALOGTV_FP_START) sig[i++]=ANALOGTV_BLACK_LEVEL;
+    }
+    while (i<ANALOGTV_H) sig[i++]=ANALOGTV_BLANK_LEVEL;
+
+    if (do_cb) {
+      /* 9 cycles of colorburst */
+      for (i=ANALOGTV_CB_START; i<ANALOGTV_CB_START+36; i+=4) {
+        sig[i+1] += ANALOGTV_CB_LEVEL;
+        sig[i+3] -= ANALOGTV_CB_LEVEL;
+      }
+    }
+  }
+}
+
+void
+analogtv_sync(analogtv *it)
+{
+  int cur_hsync=it->cur_hsync;
+  int cur_vsync=it->cur_vsync;
+  int lineno;
+  int i,j;
+  double osc,filt;
+  double *sp;
+  double cbfc=1.0/128.0;
+
+  sp = it->rx_signal + lineno*ANALOGTV_H + cur_hsync;
+  for (i=-32; i<32; i++) {
+    lineno = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V;
+    sp = it->rx_signal + lineno*ANALOGTV_H;
+    filt=0.0;
+    for (j=0; j<ANALOGTV_H; j+=ANALOGTV_H/16) {
+      filt += sp[j];
+    }
+    filt *= it->agclevel;
+
+    osc = (double)(ANALOGTV_V+i)/(double)ANALOGTV_V;
+
+    if (osc >= 1.05+0.0002 * filt) break;
+  }
+  cur_vsync = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V;
+
+  for (lineno=0; lineno<ANALOGTV_V; lineno++) {
+
+    if (lineno>5 && lineno<ANALOGTV_V-3) { /* ignore vsync interval */
+
+      sp = it->rx_signal + ((lineno + cur_vsync + ANALOGTV_V)%ANALOGTV_V
+                            )*ANALOGTV_H + cur_hsync;
+      for (i=-8; i<8; i++) {
+        osc = (double)(ANALOGTV_H+i)/(double)ANALOGTV_H;
+        filt=(sp[i-3]+sp[i-2]+sp[i-1]+sp[i]) * it->agclevel;
+
+        if (osc >= 1.005 + 0.0001*filt) break;
+      }
+      cur_hsync = (cur_hsync + i + ANALOGTV_H) % ANALOGTV_H;
+    }
+
+    it->line_hsync[lineno]=(cur_hsync + ANALOGTV_PIC_START +
+                            ANALOGTV_H) % ANALOGTV_H;
+
+    /* Now look for the colorburst, which is a few cycles after the H
+       sync pulse, and store its phase.
+       The colorburst is 9 cycles long, and we look at the middle 5
+       cycles.
+    */
+
+    if (lineno>15) {
+      sp = it->rx_signal + lineno*ANALOGTV_H + (cur_hsync&~3);
+      for (i=ANALOGTV_CB_START+8; i<ANALOGTV_CB_START+36-8; i++) {
+        it->cb_phase[i&3] = it->cb_phase[i&3]*(1.0-cbfc) +
+          sp[i]*it->agclevel*cbfc;
+      }
+    }
+
+    {
+      double tot=0.1;
+      double cbgain;
+
+      for (i=0; i<4; i++) {
+        tot += it->cb_phase[i]*it->cb_phase[i];
+      }
+      cbgain = 32.0/sqrt(tot);
+
+      for (i=0; i<4; i++) {
+        it->line_cb_phase[lineno][i]=it->cb_phase[i]*cbgain;
+      }
+    }
+
+#ifdef DEBUG
+    if (0) printf("hs=%d cb=[%0.3f %0.3f %0.3f %0.3f]\n",
+                  cur_hsync,
+                  it->cb_phase[0], it->cb_phase[1],
+                  it->cb_phase[2], it->cb_phase[3]);
+#endif
+
+    /* if (random()%2000==0) cur_hsync=random()%ANALOGTV_H; */
+  }
+
+  it->cur_hsync = cur_hsync;
+  it->cur_vsync = cur_vsync;
+}
+
+static double
+analogtv_levelmult(analogtv *it, int level)
+{
+  static double levelfac[3]={-7.5, 5.5, 24.5};
+  return (40.0 + levelfac[level]*puramp(it, 3.0, 6.0, 1.0))/256.0;
+}
+
+static int
+analogtv_level(analogtv *it, int y, int ytop, int ybot)
+{
+  int level;
+  if (ybot-ytop>=7) {
+    if (y==ytop || y==ybot-1) level=0;
+    else if (y==ytop+1 || y==ybot-2) level=1;
+    else level=2;
+  }
+  else if (ybot-ytop>=5) {
+    if (y==ytop || y==ybot-1) level=0;
+    else level=2;
+  }
+  else if (ybot-ytop>=3) {
+    if (y==ytop) level=0;
+    else level=2;
+  }
+  else {
+    level=2;
+  }
+  return level;
+}
+
+static void
+analogtv_blast_imagerow(analogtv *it,
+                        float *rgbf, float *rgbf_end,
+                        int ytop, int ybot)
+{
+  int i,j,x,y;
+  float *rpf;
+  char *level_copyfrom[3];
+  int xrepl=it->xrepl;
+  for (i=0; i<3; i++) level_copyfrom[i]=NULL;
+
+  for (y=ytop; y<ybot; y++) {
+    int level=analogtv_level(it, y, ytop, ybot);
+    char *rowdata;
+
+    rowdata=it->image->data + y*it->image->bytes_per_line;
+
+    /* Fast special cases to avoid the slow XPutPixel. Ugh. It goes to show
+       why standard graphics sw has to be fast, or else people will have to
+       work around it and risk incompatibility. The quickdraw folks
+       understood this. The other answer would be for X11 to have fewer
+       formats for bitm.. oh, never mind. If neither of these cases work
+       (they probably cover 99% of setups) it falls back on the Xlib
+       routines. */
+
+    if (level_copyfrom[level]) {
+      memcpy(rowdata, level_copyfrom[level], it->image->bytes_per_line);
+    }
+    else {
+      double levelmult=analogtv_levelmult(it, level);
+      level_copyfrom[level] = rowdata;
+
+      if (0) {
+      }
+      else if (it->image->format==ZPixmap &&
+               it->image->bits_per_pixel==32 &&
+               sizeof(unsigned int)==4 &&
+               it->image->byte_order==localbyteorder) {
+        /* int is more likely to be 32 bits than long */
+        unsigned int *pixelptr=(unsigned int *)rowdata;
+        unsigned int pix;
+
+        for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) {
+          int ntscri=rpf[0]*levelmult;
+          int ntscgi=rpf[1]*levelmult;
+          int ntscbi=rpf[2]*levelmult;
+          if (ntscri>=ANALOGTV_CV_MAX) ntscri=ANALOGTV_CV_MAX-1;
+          if (ntscgi>=ANALOGTV_CV_MAX) ntscgi=ANALOGTV_CV_MAX-1;
+          if (ntscbi>=ANALOGTV_CV_MAX) ntscbi=ANALOGTV_CV_MAX-1;
+          pix = (it->red_values[ntscri] |
+                 it->green_values[ntscgi] |
+                 it->blue_values[ntscbi]);
+          pixelptr[0] = pix;
+          if (xrepl>=2) {
+            pixelptr[1] = pix;
+            if (xrepl>=3) pixelptr[2] = pix;
+          }
+          pixelptr+=xrepl;
+        }
+      }
+      else if (it->image->format==ZPixmap &&
+               it->image->bits_per_pixel==16 &&
+               sizeof(unsigned short)==2 &&
+               float_extraction_works &&
+               it->image->byte_order==localbyteorder) {
+        unsigned short *pixelptr=(unsigned short *)rowdata;
+        double r2,g2,b2;
+        float_extract_t r1,g1,b1;
+        unsigned short pix;
+
+        for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) {
+          r2=rpf[0]; g2=rpf[1]; b2=rpf[2];
+          r1.f=r2 * levelmult+float_low8_ofs;
+          g1.f=g2 * levelmult+float_low8_ofs;
+          b1.f=b2 * levelmult+float_low8_ofs;
+          pix = (it->red_values[r1.i & 0x3ff] |
+                 it->green_values[g1.i & 0x3ff] |
+                 it->blue_values[b1.i & 0x3ff]);
+          pixelptr[0] = pix;
+          if (xrepl>=2) {
+            pixelptr[1] = pix;
+            if (xrepl>=3) pixelptr[2] = pix;
+          }
+          pixelptr+=xrepl;
+        }
+      }
+      else if (it->image->format==ZPixmap &&
+               it->image->bits_per_pixel==16 &&
+               sizeof(unsigned short)==2 &&
+               it->image->byte_order==localbyteorder) {
+        unsigned short *pixelptr=(unsigned short *)rowdata;
+        unsigned short pix;
+
+        for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) {
+          int r1=rpf[0] * levelmult;
+          int g1=rpf[1] * levelmult;
+          int b1=rpf[2] * levelmult;
+          if (r1>=ANALOGTV_CV_MAX) r1=ANALOGTV_CV_MAX-1;
+          if (g1>=ANALOGTV_CV_MAX) g1=ANALOGTV_CV_MAX-1;
+          if (b1>=ANALOGTV_CV_MAX) b1=ANALOGTV_CV_MAX-1;
+          pix = it->red_values[r1] | it->green_values[g1] | it->blue_values[b1];
+          pixelptr[0] = pix;
+          if (xrepl>=2) {
+            pixelptr[1] = pix;
+            if (xrepl>=3) pixelptr[2] = pix;
+          }
+          pixelptr+=xrepl;
+        }
+      }
+      else {
+        for (x=0, rpf=rgbf; rpf!=rgbf_end ; x++, rpf+=3) {
+          int ntscri=rpf[0]*levelmult;
+          int ntscgi=rpf[1]*levelmult;
+          int ntscbi=rpf[2]*levelmult;
+          if (ntscri>=ANALOGTV_CV_MAX) ntscri=ANALOGTV_CV_MAX-1;
+          if (ntscgi>=ANALOGTV_CV_MAX) ntscgi=ANALOGTV_CV_MAX-1;
+          if (ntscbi>=ANALOGTV_CV_MAX) ntscbi=ANALOGTV_CV_MAX-1;
+          for (j=0; j<xrepl; j++) {
+            XPutPixel(it->image, x*xrepl + j, y,
+                      it->red_values[ntscri] | it->green_values[ntscgi] |
+                      it->blue_values[ntscbi]);
+          }
+        }
+      }
+    }
+  }
+}
+
+void
+analogtv_draw(analogtv *it)
+{
+  int i,j,x,y,lineno;
+  int scanstart_i,scanend_i,squishright_i,squishdiv,pixrate;
+  float *rgb_start, *rgb_end;
+  double pixbright;
+  int pixmultinc;
+  int bigloadchange,drawcount;
+  double baseload;
+  double puheight;
+  int overall_top, overall_bot;
+
+  float *raw_rgb_start=(float *)calloc(it->subwidth*3, sizeof(float));
+  float *raw_rgb_end=raw_rgb_start+3*it->subwidth;
+  float *rrp;
+
+  analogtv_setup_frame(it);
+  analogtv_set_demod(it);
+
+  /* rx_signal has an extra 2 lines at the end, where we copy the
+     first 2 lines so we can index into it while only worrying about
+     wraparound on a per-line level */
+  memcpy(&it->rx_signal[ANALOGTV_SIGNAL_LEN],
+         &it->rx_signal[0],
+         2*ANALOGTV_H*sizeof(it->rx_signal[0]));
+
+  analogtv_sync(it);
+
+  baseload=0.5;
+  /* if (it->hashnoise_on) baseload=0.5; */
+
+  bigloadchange=1;
+  drawcount=0;
+  it->crtload[ANALOGTV_TOP-1]=baseload;
+  puheight = puramp(it, 2.0, 1.0, 1.3) * it->height_control *
+    (1.125 - 0.125*puramp(it, 2.0, 2.0, 1.1));
+
+  overall_top=it->useheight;
+  overall_bot=0;
+
+  for (lineno=ANALOGTV_TOP; lineno<ANALOGTV_BOT; lineno++) {
+    int slineno=lineno-ANALOGTV_TOP;
+    int ytop=(int)((slineno*it->useheight/ANALOGTV_VISLINES -
+                    it->useheight/2)*puheight) + it->useheight/2;
+    int ybot=(int)(((slineno+1)*it->useheight/ANALOGTV_VISLINES -
+                    it->useheight/2)*puheight) + it->useheight/2;
+#if 0
+    int linesig=analogtv_line_signature(input,lineno)
+      + it->hashnoise_times[lineno];
+#endif
+    double *signal=(it->rx_signal + ((lineno + it->cur_vsync +
+                                      ANALOGTV_V)%ANALOGTV_V) * ANALOGTV_H +
+                    it->line_hsync[lineno]);
+
+    if (ytop==ybot) continue;
+    if (ybot<0 || ytop>it->useheight) continue;
+    if (ytop<0) ytop=0;
+    if (ybot>it->useheight) ybot=it->useheight;
+
+    if (ytop < overall_top) overall_top=ytop;
+    if (ybot > overall_bot) overall_bot=ybot;
+
+    if (lineno==it->shrinkpulse) {
+      baseload += 0.4;
+      bigloadchange=1;
+      it->shrinkpulse=-1;
+    }
+
+#if 0
+    if (it->hashnoise_rpm>0.0 &&
+        !(bigloadchange ||
+          it->redraw_all ||
+          (slineno<20 && it->flutter_horiz_desync) ||
+          it->gaussiannoise_level>30 ||
+          ((it->gaussiannoise_level>2.0 ||
+            it->multipath) && random()%4) ||
+          linesig != it->onscreen_signature[lineno])) {
+      continue;
+    }
+    it->onscreen_signature[lineno] = linesig;
+#endif
+    drawcount++;
+
+    /*
+      Interpolate the 600-dotclock line into however many horizontal
+      screen pixels we're using, and convert to RGB.
+
+      We add some 'bloom', variations in the horizontal scan width with
+      the amount of brightness, extremely common on period TV sets. They
+      had a single oscillator which generated both the horizontal scan and
+      (during the horizontal retrace interval) the high voltage for the
+      electron beam. More brightness meant more load on the oscillator,
+      which caused an decrease in horizontal deflection. Look for
+      (bloomthisrow).
+
+      Also, the A2 did a bad job of generating horizontal sync pulses
+      during the vertical blanking interval. This, and the fact that the
+      horizontal frequency was a bit off meant that TVs usually went a bit
+      out of sync during the vertical retrace, and the top of the screen
+      would be bent a bit to the left or right. Look for (shiftthisrow).
+
+      We also add a teeny bit of left overscan, just enough to be
+      annoying, but you can still read the left column of text.
+
+      We also simulate compression & brightening on the right side of the
+      screen. Most TVs do this, but you don't notice because they overscan
+      so it's off the right edge of the CRT. But the A2 video system used
+      so much of the horizontal scan line that you had to crank the
+      horizontal width down in order to not lose the right few characters,
+      and you'd see the compression on the right edge. Associated with
+      compression is brightening; since the electron beam was scanning
+      slower, the same drive signal hit the phosphor harder. Look for
+      (squishright_i) and (squishdiv).
+    */
+
+    {
+      int totsignal=0;
+      double ncl,diff;
+
+      for (i=0; i<ANALOGTV_PIC_LEN; i++) {
+        totsignal += signal[i];
+      }
+      totsignal *= it->agclevel;
+      ncl = 0.95 * it->crtload[lineno-1] +
+        0.05*(baseload +
+              (totsignal-30000)/100000.0 +
+              (slineno>184 ? (slineno-184)*(lineno-184)*0.001 * it->squeezebottom
+               : 0.0));
+      diff=ncl - it->crtload[lineno];
+      bigloadchange = (diff>0.01 || diff<-0.01);
+      it->crtload[lineno]=ncl;
+    }
+
+    {
+      double bloomthisrow,shiftthisrow;
+      double viswidth,middle;
+      double scanwidth;
+      int scw,scl,scr;
+
+      bloomthisrow = -10.0 * it->crtload[lineno];
+      if (bloomthisrow<-10.0) bloomthisrow=-10.0;
+      if (bloomthisrow>2.0) bloomthisrow=2.0;
+      if (slineno<16) {
+        shiftthisrow=it->horiz_desync * (exp(-0.17*slineno) *
+                                         (0.7+cos(slineno*0.6)));
+      } else {
+        shiftthisrow=0.0;
+      }
+
+      viswidth=ANALOGTV_PIC_LEN * 0.79 - 5.0*bloomthisrow;
+      middle=ANALOGTV_PIC_LEN/2 - shiftthisrow;
+
+      scanwidth=it->width_control * puramp(it, 0.5, 0.3, 1.0);
+
+      scw=it->subwidth*scanwidth;
+      if (scw>it->subwidth) scw=it->usewidth;
+      scl=it->subwidth/2 - scw/2;
+      scr=it->subwidth/2 + scw/2;
+
+      pixrate=(int)((viswidth*65536.0*1.0)/it->subwidth)/scanwidth;
+      scanstart_i=(int)((middle-viswidth*0.5)*65536.0);
+      scanend_i=(ANALOGTV_PIC_LEN-1)*65536;
+      squishright_i=(int)((middle+viswidth*(0.25 + 0.25*puramp(it, 2.0, 0.0, 1.1)
+                                            - it->squish_control)) *65536.0);
+      squishdiv=it->subwidth/15;
+
+      rgb_start=raw_rgb_start+scl*3;
+      rgb_end=raw_rgb_start+scr*3;
+
+      assert(scanstart_i>=0);
+
+#ifdef DEBUG
+      if (0) printf("scan %d: %0.3f %0.3f %0.3f scl=%d scr=%d scw=%d\n",
+                    lineno,
+                    scanstart_i/65536.0,
+                    squishright_i/65536.0,
+                    scanend_i/65536.0,
+                    scl,scr,scw);
+#endif
+    }
+
+    if (it->use_cmap) {
+      for (y=ytop; y<ybot; y++) {
+        int level=analogtv_level(it, y, ytop, ybot);
+        double levelmult=analogtv_levelmult(it, level);
+        double levelmult_y = levelmult * it->contrast_control
+          * puramp(it, 1.0, 0.0, 1.0) / (0.5+0.5*puheight) * 0.070;
+        double levelmult_iq = levelmult * 0.090;
+
+        struct analogtv_yiq_s *yiq=it->yiq;
+        analogtv_ntsc_to_yiq(it, lineno, signal,
+                             (scanstart_i>>16)-10, (scanend_i>>16)+10);
+        pixmultinc=pixrate;
+
+        x=0;
+        i=scanstart_i;
+        while (i<0 && x<it->usewidth) {
+          XPutPixel(it->image, x, y, it->colors[0]);
+          i+=pixmultinc;
+          x++;
+        }
+
+        while (i<scanend_i && x<it->usewidth) {
+          double pixfrac=(i&0xffff)/65536.0;
+          double invpixfrac=(1.0-pixfrac);
+          int pati=i>>16;
+          int yli,ili,qli,cmi;
+
+          double interpy=(yiq[pati].y*invpixfrac
+                          + yiq[pati+1].y*pixfrac) * levelmult_y;
+          double interpi=(yiq[pati].i*invpixfrac
+                          + yiq[pati+1].i*pixfrac) * levelmult_iq;
+          double interpq=(yiq[pati].q*invpixfrac
+                          + yiq[pati+1].q*pixfrac) * levelmult_iq;
+
+          yli = (int)(interpy * it->cmap_y_levels);
+          ili = (int)((interpi+0.5) * it->cmap_i_levels);
+          qli = (int)((interpq+0.5) * it->cmap_q_levels);
+          if (yli<0) yli=0;
+          if (yli>=it->cmap_y_levels) yli=it->cmap_y_levels-1;
+          if (ili<0) ili=0;
+          if (ili>=it->cmap_i_levels) ili=it->cmap_i_levels-1;
+          if (qli<0) qli=0;
+          if (qli>=it->cmap_q_levels) qli=it->cmap_q_levels-1;
+
+          cmi=qli + it->cmap_i_levels*(ili + it->cmap_q_levels*yli);
+
+#ifdef DEBUG
+          if ((random()%65536)==0) {
+            printf("%0.3f %0.3f %0.3f => %d %d %d => %d\n",
+                   interpy, interpi, interpq,
+                   yli, ili, qli,
+                   cmi);
+          }
+#endif
+
+          for (j=0; j<it->xrepl; j++) {
+            XPutPixel(it->image, x, y,
+                      it->colors[cmi]);
+            x++;
+          }
+          if (i >= squishright_i) {
+            pixmultinc += pixmultinc/squishdiv;
+          }
+          i+=pixmultinc;
+        }
+        while (x<it->usewidth) {
+          XPutPixel(it->image, x, y, it->colors[0]);
+          x++;
+        }
+      }
+    }
+    else {
+      struct analogtv_yiq_s *yiq=it->yiq;
+      analogtv_ntsc_to_yiq(it, lineno, signal,
+                           (scanstart_i>>16)-10, (scanend_i>>16)+10);
+
+      pixbright=it->contrast_control * puramp(it, 1.0, 0.0, 1.0)
+        / (0.5+0.5*puheight) * 1024.0/100.0;
+      pixmultinc=pixrate;
+      i=scanstart_i; rrp=rgb_start;
+      while (i<0 && rrp!=rgb_end) {
+        rrp[0]=rrp[1]=rrp[2]=0;
+        i+=pixmultinc;
+        rrp+=3;
+      }
+      while (i<scanend_i && rrp!=rgb_end) {
+        double pixfrac=(i&0xffff)/65536.0;
+        double invpixfrac=1.0-pixfrac;
+        int pati=i>>16;
+        double r,g,b;
+
+        double interpy=(yiq[pati].y*invpixfrac + yiq[pati+1].y*pixfrac);
+        double interpi=(yiq[pati].i*invpixfrac + yiq[pati+1].i*pixfrac);
+        double interpq=(yiq[pati].q*invpixfrac + yiq[pati+1].q*pixfrac);
+
+        /*
+          According to the NTSC spec, Y,I,Q are generated as:
+
+          y=0.30 r + 0.59 g + 0.11 b
+          i=0.60 r - 0.28 g - 0.32 b
+          q=0.21 r - 0.52 g + 0.31 b
+
+          So if you invert the implied 3x3 matrix you get what standard
+          televisions implement with a bunch of resistors (or directly in the
+          CRT -- don't ask):
+
+          r = y + 0.948 i + 0.624 q
+          g = y - 0.276 i - 0.639 q
+          b = y - 1.105 i + 1.729 q
+        */
+
+        r=(interpy + 0.948*interpi + 0.624*interpq) * pixbright;
+        g=(interpy - 0.276*interpi - 0.639*interpq) * pixbright;
+        b=(interpy - 1.105*interpi + 1.729*interpq) * pixbright;
+        if (r<0.0) r=0.0;
+        if (g<0.0) g=0.0;
+        if (b<0.0) b=0.0;
+        rrp[0]=r;
+        rrp[1]=g;
+        rrp[2]=b;
+
+        if (i>=squishright_i) {
+          pixmultinc += pixmultinc/squishdiv;
+          pixbright += pixbright/squishdiv/2;
+        }
+        i+=pixmultinc;
+        rrp+=3;
+      }
+      while (rrp != rgb_end) {
+        rrp[0]=rrp[1]=rrp[2]=0.0;
+        rrp+=3;
+      }
+
+      analogtv_blast_imagerow(it, raw_rgb_start, raw_rgb_end,
+                              ytop,ybot);
+    }
+  }
+  free(raw_rgb_start);
+
+#if 0
+  /* poor attempt at visible retrace */
+  for (i=0; i<15; i++) {
+    int ytop=(int)((i*it->useheight/15 -
+                    it->useheight/2)*puheight) + it->useheight/2;
+    int ybot=(int)(((i+1)*it->useheight/15 -
+                    it->useheight/2)*puheight) + it->useheight/2;
+    int div=it->usewidth*3/2;
+
+    for (x=0; x<it->usewidth; x++) {
+      y = ytop + (ybot-ytop)*x / div;
+      if (y<0 || y>=it->useheight) continue;
+      XPutPixel(it->image, x, y, 0xffffff);
+    }
+  }
+#endif
+
+  if (it->need_clear) {
+    XClearWindow(it->dpy, it->window);
+    it->need_clear=0;
+  }
+
+  if (overall_top>0) {
+    XClearArea(it->dpy, it->window,
+               it->screen_xo, it->screen_yo,
+               it->usewidth, overall_top, 0);
+  }
+  if (it->useheight > overall_bot) {
+    XClearArea(it->dpy, it->window,
+               it->screen_xo, it->screen_yo+overall_bot,
+               it->usewidth, it->useheight-overall_bot, 0);
+  }
+
+  if (overall_bot > overall_top) {
+    if (it->use_shm) {
+#ifdef HAVE_XSHM_EXTENSION
+      XShmPutImage(it->dpy, it->window, it->gc, it->image,
+                   0, overall_top,
+                   it->screen_xo, it->screen_yo+overall_top,
+                   it->usewidth, overall_bot - overall_top,
+                   False);
+#endif
+    } else {
+      XPutImage(it->dpy, it->window, it->gc, it->image,
+                0, overall_top,
+                it->screen_xo, it->screen_yo+overall_top,
+                it->usewidth, overall_bot - overall_top);
+    }
+  }
+
+#ifdef DEBUG
+  if (0) {
+    struct timeval tv;
+    double fps;
+    char buf[256];
+    gettimeofday(&tv,NULL);
+
+    fps=1.0/((tv.tv_sec - it->last_display_time.tv_sec)
+             + 0.000001*(tv.tv_usec - it->last_display_time.tv_usec));
+    sprintf(buf, "FPS=%0.1f",fps);
+    XDrawString(it->dpy, it->window, it->gc, 50, it->useheight*2/3,
+                buf, strlen(buf));
+
+    it->last_display_time=tv;
+  }
+#endif
+
+  XSync(it->dpy,0);
+}
+
+analogtv_input *
+analogtv_input_allocate()
+{
+  analogtv_input *ret=(analogtv_input *)calloc(1,sizeof(analogtv_input));
+
+  return ret;
+}
+
+/*
+  This takes a screen image and encodes it as a video camera would,
+  including all the bandlimiting and YIQ modulation.
+  This isn't especially tuned for speed.
+*/
+
+int
+analogtv_load_ximage(analogtv *it, analogtv_input *input, XImage *pic_im)
+{
+  int i,x,y;
+  int img_w,img_h;
+  int fyx[7],fyy[7];
+  int fix[4],fiy[4];
+  int fqx[4],fqy[4];
+  XColor col1[ANALOGTV_PIC_LEN];
+  XColor col2[ANALOGTV_PIC_LEN];
+  int multiq[ANALOGTV_PIC_LEN+4];
+
+  img_w=pic_im->width;
+  img_h=pic_im->height;
+
+  for (i=0; i<ANALOGTV_PIC_LEN+4; i++) {
+    double phase=90.0-90.0*i;
+    double ampl=1.0;
+    multiq[i]=(int)(-cos(3.1415926/180.0*(phase-303)) * 4096.0 * ampl);
+  }
+
+  for (y=0; y<ANALOGTV_VISLINES; y++) {
+    int picy1=(y*img_h)/ANALOGTV_VISLINES;
+    int picy2=(y*img_h+ANALOGTV_VISLINES/2)/ANALOGTV_VISLINES;
+
+    for (x=0; x<ANALOGTV_PIC_LEN; x++) {
+      int picx=(x*img_w)/ANALOGTV_PIC_LEN;
+      col1[x].pixel=XGetPixel(pic_im, picx, picy1);
+      col2[x].pixel=XGetPixel(pic_im, picx, picy2);
+    }
+    XQueryColors(it->dpy, it->colormap, col1, ANALOGTV_PIC_LEN);
+    XQueryColors(it->dpy, it->colormap, col2, ANALOGTV_PIC_LEN);
+
+    for (i=0; i<7; i++) fyx[i]=fyy[i]=0;
+    for (i=0; i<4; i++) fix[i]=fiy[i]=fqx[i]=fqy[i]=0.0;
+
+    for (x=0; x<ANALOGTV_PIC_LEN; x++) {
+      int rawy,rawi,rawq;
+      int filty,filti,filtq;
+      int composite;
+      /* Compute YIQ as:
+           y=0.30 r + 0.59 g + 0.11 b
+           i=0.60 r - 0.28 g - 0.32 b
+           q=0.21 r - 0.52 g + 0.31 b
+          The coefficients below are in .4 format */
+
+      rawy=( 5*col1[x].red + 11*col1[x].green + 2*col1[x].blue +
+             5*col2[x].red + 11*col2[x].green + 2*col2[x].blue)>>7;
+      rawi=(10*col1[x].red -  4*col1[x].green - 5*col1[x].blue +
+            10*col2[x].red -  4*col2[x].green - 5*col2[x].blue)>>7;
+      rawq=( 3*col1[x].red -  8*col1[x].green + 5*col1[x].blue +
+             3*col2[x].red -  8*col2[x].green + 5*col2[x].blue)>>7;
+
+      /* Filter y at with a 4-pole low-pass Butterworth filter at 3.5 MHz
+         with an extra zero at 3.5 MHz, from
+         mkfilter -Bu -Lp -o 4 -a 2.1428571429e-01 0 -Z 2.5e-01 -l */
+
+      fyx[0] = fyx[1]; fyx[1] = fyx[2]; fyx[2] = fyx[3];
+      fyx[3] = fyx[4]; fyx[4] = fyx[5]; fyx[5] = fyx[6];
+      fyx[6] = (rawy * 1897) >> 16;
+      fyy[0] = fyy[1]; fyy[1] = fyy[2]; fyy[2] = fyy[3];
+      fyy[3] = fyy[4]; fyy[4] = fyy[5]; fyy[5] = fyy[6];
+      fyy[6] = (fyx[0]+fyx[6]) + 4*(fyx[1]+fyx[5]) + 7*(fyx[2]+fyx[4]) + 8*fyx[3]
+        + ((-151*fyy[2] + 8115*fyy[3] - 38312*fyy[4] + 36586*fyy[5]) >> 16);
+      filty = fyy[6];
+
+      /* Filter I at 1.5 MHz. 3 pole Butterworth from
+         mkfilter -Bu -Lp -o 3 -a 1.0714285714e-01 0 */
+
+      fix[0] = fix[1]; fix[1] = fix[2]; fix[2] = fix[3];
+      fix[3] = (rawi * 1413) >> 16;
+      fiy[0] = fiy[1]; fiy[1] = fiy[2]; fiy[2] = fiy[3];
+      fiy[3] = (fix[0]+fix[3]) + 3*(fix[1]+fix[2])
+        + ((16559*fiy[0] - 72008*fiy[1] + 109682*fiy[2]) >> 16);
+      filti = fiy[3];
+
+      /* Filter Q at 0.5 MHz. 3 pole Butterworth from
+         mkfilter -Bu -Lp -o 3 -a 3.5714285714e-02 0 -l */
+
+      fqx[0] = fqx[1]; fqx[1] = fqx[2]; fqx[2] = fqx[3];
+      fqx[3] = (rawq * 75) >> 16;
+      fqy[0] = fqy[1]; fqy[1] = fqy[2]; fqy[2] = fqy[3];
+      fqy[3] = (fqx[0]+fqx[3]) + 3 * (fqx[1]+fqx[2])
+        + ((2612*fqy[0] - 9007*fqy[1] + 10453 * fqy[2]) >> 12);
+      filtq = fqy[3];
+
+
+      composite = filty + ((multiq[x] * filti + multiq[x+3] * filtq)>>12);
+      composite = ((composite*100)>>14) + ANALOGTV_BLACK_LEVEL;
+      if (composite>125) composite=125;
+      if (composite<0) composite=0;
+      input->signal[y+ANALOGTV_TOP][x+ANALOGTV_PIC_START] = composite;
+    }
+  }
+
+  return 1;
+}
+
+#if 0
+void analogtv_channel_noise(analogtv_input *it, analogtv_input *s2)
+{
+  int x,y,newsig;
+  int change=random()%ANALOGTV_V;
+  unsigned int fastrnd=random();
+  double hso=(int)(random()%1000)-500;
+  int yofs=random()%ANALOGTV_V;
+  int noise;
+
+  for (y=change; y<ANALOGTV_V; y++) {
+    int s2y=(y+yofs)%ANALOGTV_V;
+    int filt=0;
+    int noiselevel=60000 / (y-change+100);
+
+    it->line_hsync[y] = s2->line_hsync[y] + (int)hso;
+    hso *= 0.9;
+    for (x=0; x<ANALOGTV_H; x++) {
+      FASTRND;
+      filt+= (-filt/16) + (int)(fastrnd&0xfff)-0x800;
+      noise=(filt*noiselevel)>>16;
+      newsig=s2->signal[s2y][x] + noise;
+      if (newsig>120) newsig=120;
+      if (newsig<0) newsig=0;
+      it->signal[y][x]=newsig;
+    }
+  }
+  s2->vsync=yofs;
+}
+#endif
+
+
+void analogtv_add_signal(analogtv *it, analogtv_reception *rec)
+{
+  analogtv_input *inp=rec->input;
+  double *ps=it->rx_signal;
+  double *pe=it->rx_signal + ANALOGTV_SIGNAL_LEN;
+  double *p=ps;
+  char *ss=&inp->signal[0][0];
+  char *se=&inp->signal[0][0] + ANALOGTV_SIGNAL_LEN;
+  char *s=ss + ((unsigned)rec->ofs % ANALOGTV_SIGNAL_LEN);
+  int i;
+  int ec=it->channel_change_cycles;
+  double level=rec->level;
+  double hfloss=rec->hfloss;
+  unsigned int fastrnd=random();
+  double dp[8];
+
+  /* assert((se-ss)%4==0 && (se-s)%4==0); */
+
+  /* duplicate the first line into the Nth line to ease wraparound computation */
+  memcpy(inp->signal[ANALOGTV_V], inp->signal[0],
+         ANALOGTV_H * sizeof(inp->signal[0][0]));
+
+  for (i=0; i<8; i++) dp[i]=0.0;
+
+  if (ec) {
+    double noise_ampl;
+
+    /* Do a big noisy transition. We can make the transition noise of
+       high constant strength regardless of signal strength.
+
+       There are two separate state machines. here, One is the noise
+       process and the other is the
+
+       We don't bother with the FIR filter here
+    */
+
+    noise_ampl = 1.3;
+
+    while (p!=pe && ec>0) {
+
+      double sig0=(double)s[0];
+      double noise = ((int)fastrnd-(int)0x7fffffff) * (50.0/(double)0x7fffffff);
+      fastrnd = (fastrnd*1103515245+12345) & 0xffffffffu;
+
+      p[0] += sig0 * level * (1.0 - noise_ampl) + noise * noise_ampl;
+
+      noise_ampl *= 0.99995;
+
+      p++;
+      s++;
+      if (s>=se) s=ss;
+      ec--;
+    }
+
+  }
+
+  while (p != pe) {
+    double sig0,sig1,sig2,sig3,sigr;
+
+    sig0=(double)s[0];
+    sig1=(double)s[1];
+    sig2=(double)s[2];
+    sig3=(double)s[3];
+
+    dp[0]=sig0+sig1+sig2+sig3;
+
+    /* Get the video out signal, and add some ghosting, typical of RF
+       monitor cables. This corresponds to a pretty long cable, but
+       looks right to me.
+    */
+
+    sigr=(dp[1]*rec->ghostfir[0] + dp[2]*rec->ghostfir[1] +
+          dp[3]*rec->ghostfir[2] + dp[4]*rec->ghostfir[3]);
+    dp[4]=dp[3]; dp[3]=dp[2]; dp[2]=dp[1]; dp[1]=dp[0];
+
+    p[0] += (sig0+sigr + sig2*hfloss) * level;
+    p[1] += (sig1+sigr + sig3*hfloss) * level;
+    p[2] += (sig2+sigr + sig0*hfloss) * level;
+    p[3] += (sig3+sigr + sig1*hfloss) * level;
+
+    p += 4;
+    s += 4;
+    if (s>=se) s = ss + (s-se);
+  }
+
+  it->rx_signal_level =
+    sqrt(it->rx_signal_level * it->rx_signal_level +
+         (level * level * (1.0 + 4.0*(rec->ghostfir[0] + rec->ghostfir[1] +
+                                      rec->ghostfir[2] + rec->ghostfir[3]))));
+
+
+  it->channel_change_cycles=0;
+
+}
+
+#ifdef FIXME
+/* add hash */
+  if (it->hashnoise_times[lineno]) {
+    int hnt=it->hashnoise_times[lineno] - input->line_hsync[lineno];
+
+    if (hnt>=0 && hnt<ANALOGTV_PIC_LEN) {
+      double maxampl=1.0;
+      double cur=frand(150.0)-20.0;
+      int len=random()%15+3;
+      if (len > ANALOGTV_PIC_LEN-hnt) len=ANALOGTV_PIC_LEN-hnt;
+      for (i=0; i<len; i++) {
+        double sig=signal[hnt];
+
+        sig += cur*maxampl;
+        cur += frand(5.0)-5.0;
+        maxampl = maxampl*0.9;
+
+        signal[hnt]=sig;
+        hnt++;
+      }
+    }
+  }
+#endif
+
+
+void analogtv_init_signal(analogtv *it, double noiselevel)
+{
+  double *ps=it->rx_signal;
+  double *pe=it->rx_signal + ANALOGTV_SIGNAL_LEN;
+  double *p=ps;
+  unsigned int fastrnd=random();
+  double nm1=0.0,nm2=0.0;
+  double noisemul = sqrt(noiselevel*150)/(double)0x7fffffff;
+
+  while (p != pe) {
+    nm2=nm1;
+    nm1 = ((int)fastrnd-(int)0x7fffffff) * noisemul;
+    *p++ = nm1*nm2;
+    fastrnd = (fastrnd*1103515245+12345) & 0xffffffffu;
+  }
+
+  it->rx_signal_level = noiselevel;
+}
+
+void
+analogtv_reception_update(analogtv_reception *rec)
+{
+  int i;
+
+  if (rec->multipath > 0.0) {
+    for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
+      rec->ghostfir2[i] +=
+        -(rec->ghostfir2[i]/16.0) + rec->multipath * (frand(0.02)-0.01);
+    }
+    if (random()%20==0) {
+      rec->ghostfir2[random()%(ANALOGTV_GHOSTFIR_LEN)]
+        = rec->multipath * (frand(0.08)-0.04);
+    }
+    for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
+      rec->ghostfir[i] = 0.8*rec->ghostfir[i] + 0.2*rec->ghostfir2[i];
+    }
+
+    if (0) {
+      rec->hfloss2 += -(rec->hfloss2/16.0) + rec->multipath * (frand(0.08)-0.04);
+      rec->hfloss = 0.5*rec->hfloss + 0.5*rec->hfloss2;
+    }
+
+  } else {
+    for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
+      rec->ghostfir[i] = (i>=ANALOGTV_GHOSTFIR_LEN/2) ? ((i&1) ? +0.04 : -0.08) /ANALOGTV_GHOSTFIR_LEN
+        : 0.0;
+    }
+  }
+}
+
+
+void
+analogtv_make_font(Display *dpy, Window window, analogtv_font *f,
+                   int w, int h, char *fontname)
+{
+  int i;
+  XFontStruct *font;
+  Pixmap text_pm;
+  GC gc;
+  XGCValues gcv;
+  XWindowAttributes xgwa;
+
+  f->char_w = w;
+  f->char_h = h;
+
+  XGetWindowAttributes (dpy, window, &xgwa);
+
+  if (fontname) {
+
+    font = XLoadQueryFont (dpy, fontname);
+    if (!font) {
+      fprintf(stderr, "analogtv: can't load font %s\n", fontname);
+      abort();
+    }
+
+    text_pm=XCreatePixmap(dpy, window, 128*f->char_w, f->char_h, xgwa.depth);
+
+    memset(&gcv, 0, sizeof(gcv));
+    gcv.foreground=1;
+    gcv.background=0;
+    gcv.font=font->fid;
+    gc=XCreateGC(dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
+
+    XSetForeground(dpy, gc, 0);
+    XFillRectangle(dpy, text_pm, gc, 0, 0, 128*f->char_w, f->char_h);
+    XSetForeground(dpy, gc, 1);
+    /* Just ASCII */
+    for (i=0; i<128; i++) {
+      char c=i;
+      int x=f->char_w*i+1;
+      int y=f->char_h*8/10;
+      XDrawString(dpy, text_pm, gc, x, y, &c, 1);
+    }
+    f->text_im = XGetImage(dpy, text_pm, 0, 0, 128*f->char_w, f->char_h,
+                           ~0L, ZPixmap);
+    XFreeGC(dpy, gc);
+    XFreePixmap(dpy, text_pm);
+  } else {
+    f->text_im = XCreateImage(dpy, xgwa.visual, xgwa.depth,
+                              ZPixmap, 0, 0,
+                              128*f->char_w, f->char_h, 8, 0);
+    f->text_im->data = (char *)calloc(f->text_im->height,
+                                      f->text_im->bytes_per_line);
+
+  }
+  f->x_mult=4;
+  f->y_mult=2;
+}
+
+int
+analogtv_font_pixel(analogtv_font *f, int c, int x, int y)
+{
+  if (x<0 || x>=f->char_w) return 0;
+  if (y<0 || y>=f->char_h) return 0;
+  if (c<0 || c>=128) return 0;
+  return XGetPixel(f->text_im, c*f->char_w + x, y) ? 1 : 0;
+}
+
+void
+analogtv_font_set_pixel(analogtv_font *f, int c, int x, int y, int value)
+{
+  if (x<0 || x>=f->char_w) return;
+  if (y<0 || y>=f->char_h) return;
+  if (c<0 || c>=128) return;
+
+  XPutPixel(f->text_im, c*f->char_w + x, y, value);
+}
+
+void
+analogtv_font_set_char(analogtv_font *f, int c, char *s)
+{
+  int value,x,y;
+
+  if (c<0 || c>=128) return;
+
+  for (y=0; y<f->char_h; y++) {
+    for (x=0; x<f->char_w; x++) {
+      if (!*s) return;
+      value=(*s==' ') ? 0 : 1;
+      analogtv_font_set_pixel(f, c, x, y, value);
+      s++;
+    }
+  }
+}
+
+void
+analogtv_lcp_to_ntsc(double luma, double chroma, double phase, int ntsc[4])
+{
+  int i;
+  for (i=0; i<4; i++) {
+    double w=90.0*i + phase;
+    double val=luma + chroma * (cos(3.1415926/180.0*w));
+    if (val<0.0) val=0.0;
+    if (val>127.0) val=127.0;
+    ntsc[i]=(int)val;
+  }
+}
+
+void
+analogtv_draw_solid(analogtv_input *input,
+                    int left, int right, int top, int bot,
+                    int ntsc[4])
+{
+  int x,y;
+
+  if (right-left<4) right=left+4;
+  if (bot-top<1) bot=top+1;
+
+  for (y=top; y<bot; y++) {
+    for (x=left; x<right; x++) {
+      input->signal[y][x] = ntsc[x&3];
+    }
+  }
+}
+
+
+void
+analogtv_draw_solid_rel_lcp(analogtv_input *input,
+                            double left, double right, double top, double bot,
+                            double luma, double chroma, double phase)
+{
+  int ntsc[4];
+
+  int topi=(int)(ANALOGTV_TOP + ANALOGTV_VISLINES*top);
+  int boti=(int)(ANALOGTV_TOP + ANALOGTV_VISLINES*bot);
+  int lefti=(int)(ANALOGTV_VIS_START + ANALOGTV_VIS_LEN*left);
+  int righti=(int)(ANALOGTV_VIS_START + ANALOGTV_VIS_LEN*right);
+
+  analogtv_lcp_to_ntsc(luma, chroma, phase, ntsc);
+  analogtv_draw_solid(input, lefti, righti, topi, boti, ntsc);
+}
+
+
+void
+analogtv_draw_char(analogtv_input *input, analogtv_font *f,
+                   int c, int x, int y, int ntsc[4])
+{
+  int yc,xc,ys,xs,pix;
+
+  for (yc=0; yc<f->char_h; yc++) {
+    for (ys=y + yc*f->y_mult; ys<y + (yc+1)*f->y_mult; ys++) {
+      if (ys<0 || ys>=ANALOGTV_V) continue;
+
+      for (xc=0; xc<f->char_w; xc++) {
+        pix=analogtv_font_pixel(f, c, xc, yc);
+
+        for (xs=x + xc*f->x_mult; xs<x + (xc+1)*f->x_mult; xs++) {
+          if (xs<0 || xs>=ANALOGTV_H) continue;
+          if (pix) {
+            input->signal[ys][xs] = ntsc[xs&3];
+          }
+        }
+      }
+    }
+  }
+}
+
+void
+analogtv_draw_string(analogtv_input *input, analogtv_font *f,
+                     char *s, int x, int y, int ntsc[4])
+{
+  while (*s) {
+    analogtv_draw_char(input, f, *s, x, y, ntsc);
+    x += f->char_w * 4;
+    s++;
+  }
+}
+
+void
+analogtv_draw_string_centered(analogtv_input *input, analogtv_font *f,
+                              char *s, int x, int y, int ntsc[4])
+{
+  int width=strlen(s) * f->char_w * 4;
+  x -= width/2;
+
+  analogtv_draw_string(input, f, s, x, y, ntsc);
+}
+
+
+static const char hextonib[128] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                                   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                                   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                                   0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
+                                   0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
+                                   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                                   0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
+                                   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+/*
+  Much of this function was adapted from logo.c
+ */
+void
+analogtv_draw_xpm(analogtv *tv, analogtv_input *input,
+                  const char * const *xpm, int left, int top)
+{
+  int xpmw,xpmh;
+  int x,y,tvx,tvy,i;
+  int rawy,rawi,rawq;
+  int ncolors, nbytes;
+  char dummyc;
+  struct {
+    int r; int g; int b;
+  } cmap[256];
+
+
+  if (4 != sscanf ((const char *) *xpm,
+                   "%d %d %d %d %c",
+                   &xpmw, &xpmh, &ncolors, &nbytes, &dummyc))
+    abort();
+  if (ncolors < 1 || ncolors > 255)
+    abort();
+  if (nbytes != 1) /* a serious limitation */
+    abort();
+  xpm++;
+
+  for (i = 0; i < ncolors; i++) {
+    const char *line = *xpm;
+    int colori = ((unsigned char)*line++)&0xff;
+    while (*line)
+      {
+        int r, g, b;
+        char which;
+        while (*line == ' ' || *line == '\t')
+          line++;
+        which = *line++;
+        if (which != 'c' && which != 'm')
+          abort();
+        while (*line == ' ' || *line == '\t')
+          line++;
+        if (!strncasecmp(line, "None", 4))
+          {
+            r = g = b = -1;
+            line += 4;
+          }
+        else
+          {
+            if (*line == '#')
+              line++;
+            r = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
+            line += 2;
+            g = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
+            line += 2;
+            b = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
+            line += 2;
+          }
+
+        if (which == 'c')
+          {
+            cmap[colori].r = r;
+            cmap[colori].g = g;
+            cmap[colori].b = b;
+          }
+      }
+
+    xpm++;
+  }
+
+  for (y=0; y<xpmh; y++) {
+    const char *line = *xpm++;
+    tvy=y+top;
+    if (tvy<ANALOGTV_TOP || tvy>=ANALOGTV_BOT) continue;
+
+    for (x=0; x<xpmw; x++) {
+      int cbyte=((unsigned char)line[x])&0xff;
+      int ntsc[4];
+      tvx=x*4+left;
+      if (tvx<ANALOGTV_PIC_START || tvx+4>ANALOGTV_PIC_END) continue;
+
+      rawy=( 5*cmap[cbyte].r + 11*cmap[cbyte].g + 2*cmap[cbyte].b) / 64;
+      rawi=(10*cmap[cbyte].r -  4*cmap[cbyte].g - 5*cmap[cbyte].b) / 64;
+      rawq=( 3*cmap[cbyte].r -  8*cmap[cbyte].g + 5*cmap[cbyte].b) / 64;
+
+      ntsc[0]=rawy+rawq;
+      ntsc[1]=rawy-rawi;
+      ntsc[2]=rawy-rawq;
+      ntsc[3]=rawy+rawi;
+
+      for (i=0; i<4; i++) {
+        if (ntsc[i]>ANALOGTV_WHITE_LEVEL) ntsc[i]=ANALOGTV_WHITE_LEVEL;
+        if (ntsc[i]<ANALOGTV_BLACK_LEVEL) ntsc[i]=ANALOGTV_BLACK_LEVEL;
+      }
+
+      input->signal[tvy][tvx+0]= ntsc[(tvx+0)&3];
+      input->signal[tvy][tvx+1]= ntsc[(tvx+1)&3];
+      input->signal[tvy][tvx+2]= ntsc[(tvx+2)&3];
+      input->signal[tvy][tvx+3]= ntsc[(tvx+3)&3];
+    }
+  }
+}
+
+extern XtAppContext app;
+
+int
+analogtv_handle_events (analogtv *it)
+{
+  XSync(it->dpy, False);
+  if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
+    XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
+
+  while (XPending (it->dpy))
+    {
+      XEvent event;
+      XNextEvent (it->dpy, &event);
+      switch (event.xany.type)
+        {
+        case ButtonPress:
+          return 1;
+
+        case KeyPress:
+          {
+            KeySym keysym;
+            char c = 0;
+            XLookupString (&event.xkey, &c, 1, &keysym, 0);
+            if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
+              return 1;
+          }
+          break;
+
+          /* I don't seem to get an event when clicking the "full
+             screen" window manager icon, at least when using
+             metacity. Thus, it doesn't change the video size. Is this
+             some separate WM_* message I have to deal with?
+          */
+        case ConfigureNotify:
+          if (event.xconfigure.width  != it->xgwa.width ||
+              event.xconfigure.height != it->xgwa.height)
+            analogtv_reconfigure(it);
+          break;
+
+        case Expose:
+        case GraphicsExpose:
+          it->need_clear=1;
+          break;
+
+        default:
+          break;
+        }
+      if (it->event_handler) {
+        (*it->event_handler) (it->dpy, &event);
+      }
+    }
+  return 0;
+}
+