From http://www.jwz.org/xscreensaver/xscreensaver-5.27.tar.gz
[xscreensaver] / hacks / analogtv.c
index 1ff995d2ccef12ac4f2f52cb5abc860cce45418b..8d64d50370558ccfe04af87c8d4b4e7fa70b7010 100644 (file)
   Trevor Blackwell <tlb@tlb.org>
 */
 
+/*
+  2014-04-20, Dave Odell <dmo2118@gmail.com>:
+  API change: Folded analogtv_init_signal and *_add_signal into *_draw().
+  Added SMP support.
+  Replaced doubles with floats, including constants and transcendental functions.
+  Fixed a bug or two.
+*/
+
 #ifdef HAVE_COCOA
 # include "jwxyz.h"
 #else /* !HAVE_COCOA */
 #endif
 
 #include <assert.h>
+#include <errno.h>
 #include "utils.h"
 #include "resources.h"
 #include "analogtv.h"
 #include "yarandom.h"
 #include "grabscreen.h"
+#include "visual.h"
 
 /* #define DEBUG 1 */
 
@@ -93,20 +103,22 @@ do { \
 #endif
 
 
-#define FASTRND (fastrnd = fastrnd*1103515245+12345)
+#define FASTRND_A 1103515245
+#define FASTRND_C 12345
+#define FASTRND (fastrnd = fastrnd*FASTRND_A+FASTRND_C)
 
-static void analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
-                                 int start, int end);
+static void analogtv_ntsc_to_yiq(const analogtv *it, int lineno, const float *signal,
+                                 int start, int end, struct analogtv_yiq_s *it_yiq);
 
-static double puramp(analogtv *it, double tc, double start, double over)
+static float puramp(const analogtv *it, float tc, float start, float 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;
+  float pt=it->powerup-start;
+  float ret;
+  if (pt<0.0f) return 0.0f;
+  if (pt>900.0f || pt/tc>8.0f) return 1.0f;
 
-  ret=(1.0-exp(-pt/tc))*over;
-  if (ret>1.0) return 1.0;
+  ret=(1.0f-expf(-pt/tc))*over;
+  if (ret>1.0f) return 1.0f;
   return ret*ret;
 }
 
@@ -249,6 +261,8 @@ analogtv_free_image(analogtv *it)
       destroy_xshm_image(it->dpy, it->image, &it->shm_info);
 #endif
     } else {
+      thread_free(it->image->data);
+      it->image->data = NULL;
       XDestroyImage(it->image);
     }
     it->image=NULL;
@@ -258,19 +272,40 @@ analogtv_free_image(analogtv *it)
 static void
 analogtv_alloc_image(analogtv *it)
 {
+  /* On failure, it->image is NULL. */
+
+  unsigned bits_per_pixel = get_bits_per_pixel(it->dpy, it->xgwa.depth);
+  unsigned align = thread_memory_alignment(it->dpy) * 8 - 1;
+  /* Width is in bits. */
+  unsigned width = (it->usewidth * bits_per_pixel + align) & ~align;
+
   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);
+                                &it->shm_info,
+                                width / bits_per_pixel, 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 *)malloc(it->image->height * it->image->bytes_per_line);
+                             it->usewidth, it->useheight, 8, width / 8);
+    if (it->image) {
+      if(thread_malloc((void **)&it->image->data, it->dpy,
+                       it->image->height * it->image->bytes_per_line)) {
+        it->image->data = NULL;
+        XDestroyImage(it->image);
+        it->image = NULL;
+      }
+    }
+  }
+
+  if (it->image) {
+    memset (it->image->data, 0, it->image->height * it->image->bytes_per_line);
+  } else {
+    /* Not enough memory. Maybe try a smaller window. */
+    fprintf(stderr, "analogtv: %s\n", strerror(ENOMEM));
   }
-  memset (it->image->data, 0, it->image->height * it->image->bytes_per_line);
 }
 
 
@@ -306,8 +341,8 @@ analogtv_configure(analogtv *it)
 
 #ifdef USE_IPHONE
   /* Fill the whole iPhone screen, even though that distorts the image. */
-  min_ratio = 320.0 / 480.0 * (1 - percent);
-  max_ratio = 480.0 / 320.0 * (1 + percent);
+  min_ratio = 640.0 / 1136.0 * (1 - percent);
+  max_ratio = 1136.0 / 640.0 * (1 + percent);
 #endif
 
   if (wlim < 266 || hlim < 200)
@@ -384,20 +419,86 @@ analogtv_reconfigure(analogtv *it)
   analogtv_configure(it);
 }
 
+/* Can be any power-of-two <= 32. 16 a slightly better choice for 2-3 threads. */
+#define ANALOGTV_SUBTOTAL_LEN 32
+
+typedef struct analogtv_thread_s
+{
+  analogtv *it;
+  unsigned thread_id;
+  size_t signal_start, signal_end;
+} analogtv_thread;
+
+#define SIGNAL_OFFSET(thread_id) \
+  ((ANALOGTV_SIGNAL_LEN * (thread_id) / threads->count) & align)
+
+static int analogtv_thread_create(void *thread_raw, struct threadpool *threads,
+                                  unsigned thread_id)
+{
+  analogtv_thread *thread = (analogtv_thread *)thread_raw;
+  unsigned align;
+
+  thread->it = GET_PARENT_OBJ(analogtv, threads, threads);
+  thread->thread_id = thread_id;
+
+  align = thread_memory_alignment(thread->it->dpy) /
+            sizeof(thread->it->signal_subtotals[0]);
+  if (!align)
+    align = 1;
+  align = ~(align * ANALOGTV_SUBTOTAL_LEN - 1);
+
+  thread->signal_start = SIGNAL_OFFSET(thread_id);
+  thread->signal_end = thread_id + 1 == threads->count ?
+                       ANALOGTV_SIGNAL_LEN :
+                       SIGNAL_OFFSET(thread_id + 1);
+
+  return 0;
+}
+
+static void analogtv_thread_destroy(void *thread_raw)
+{
+}
+
 analogtv *
 analogtv_allocate(Display *dpy, Window window)
 {
+  static const struct threadpool_class cls = {
+    sizeof(analogtv_thread),
+    analogtv_thread_create,
+    analogtv_thread_destroy
+  };
+
   XGCValues gcv;
   analogtv *it=NULL;
   int i;
+  const size_t rx_signal_len = ANALOGTV_SIGNAL_LEN + 2*ANALOGTV_H;
 
   analogtv_init();
 
   it=(analogtv *)calloc(1,sizeof(analogtv));
   if (!it) return 0;
+  it->threads.count=0;
+  it->rx_signal=NULL;
+  it->signal_subtotals=NULL;
+
   it->dpy=dpy;
   it->window=window;
 
+  if (thread_malloc((void **)&it->rx_signal, dpy,
+                    sizeof(it->rx_signal[0]) * rx_signal_len))
+    goto fail;
+
+  assert(!(ANALOGTV_SIGNAL_LEN % ANALOGTV_SUBTOTAL_LEN));
+  if (thread_malloc((void **)&it->signal_subtotals, dpy,
+                    sizeof(it->signal_subtotals[0]) *
+                     (rx_signal_len / ANALOGTV_SUBTOTAL_LEN)))
+    goto fail;
+
+  if (threadpool_create(&it->threads, &cls, dpy, hardware_concurrency(dpy)))
+    goto fail;
+
+  assert(it->threads.count);
+
   it->shrinkpulse=-1;
 
   it->n_colors=0;
@@ -479,7 +580,13 @@ analogtv_allocate(Display *dpy, Window window)
   return it;
 
  fail:
-  if (it) free(it);
+  if (it) {
+    if(it->threads.count)
+      threadpool_destroy(&it->threads);
+    thread_free(it->signal_subtotals);
+    thread_free(it->rx_signal);
+    free(it);
+  }
   return NULL;
 }
 
@@ -492,6 +599,8 @@ analogtv_release(analogtv *it)
       destroy_xshm_image(it->dpy, it->image, &it->shm_info);
 #endif
     } else {
+      thread_free(it->image->data);
+      it->image->data = NULL;
       XDestroyImage(it->image);
     }
     it->image=NULL;
@@ -500,6 +609,9 @@ analogtv_release(analogtv *it)
   it->gc=NULL;
   if (it->n_colors) XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L);
   it->n_colors=0;
+  threadpool_destroy(&it->threads);
+  thread_free(it->rx_signal);
+  thread_free(it->signal_subtotals);
   free(it);
 }
 
@@ -656,19 +768,19 @@ analogtv_line_signature(analogtv_input *input, int lineno)
 */
 
 static void
-analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
-                     int start, int end)
+analogtv_ntsc_to_yiq(const analogtv *it, int lineno, const float *signal,
+                     int start, int end, struct analogtv_yiq_s *it_yiq)
 {
   enum {MAXDELAY=32};
   int i;
-  double *sp;
+  const float *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];
+  float agclevel=it->agclevel;
+  float brightadd=it->brightness_control*100.0 - ANALOGTV_BLACK_LEVEL;
+  float delay[MAXDELAY+ANALOGTV_PIC_LEN], *dp;
+  float multiq2[4];
 
   {
 
@@ -703,14 +815,14 @@ analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
 #endif
 
   dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
-  for (i=0; i<5; i++) dp[i]=0.0;
+  for (i=0; i<5; i++) dp[i]=0.0f;
 
   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;
+  for (i=start, yiq=it_yiq+start, sp=signal+start;
        i<end;
        i++, dp--, yiq++, sp++) {
 
@@ -730,13 +842,13 @@ analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
        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]);
+    dp[0] = sp[0] * 0.0469904257251935f * agclevel;
+    dp[8] = (+1.0f*(dp[6]+dp[0])
+             +4.0f*(dp[5]+dp[1])
+             +7.0f*(dp[4]+dp[2])
+             +8.0f*(dp[3])
+             -0.0176648f*dp[12]
+             -0.4860288f*dp[10]);
     yiq->y = dp[8] + brightadd;
   }
 
@@ -744,10 +856,10 @@ analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
     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;
+    for (i=start, yiq=it_yiq+start, sp=signal+start;
          i<end;
          i++, dp--, yiq++, sp++) {
-      double sig=*sp;
+      float 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
@@ -755,21 +867,21 @@ analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
          Delay about 3.
       */
 
-      dp[0] = sig*multiq2[i&3] * 0.0833333333333;
+      dp[0] = sig*multiq2[i&3] * 0.0833333333333f;
       yiq->i=dp[8] = (dp[5] + dp[0]
-                      +3.0*(dp[4] + dp[1])
-                      +4.0*(dp[3] + dp[2])
-                      -0.3333333333 * dp[10]);
+                      +3.0f*(dp[4] + dp[1])
+                      +4.0f*(dp[3] + dp[2])
+                      -0.3333333333f * dp[10]);
 
-      dp[16] = sig*multiq2[(i+3)&3] * 0.0833333333333;
+      dp[16] = sig*multiq2[(i+3)&3] * 0.0833333333333f;
       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]);
+                       +3.0f*(dp[16+4] + dp[16+1])
+                       +4.0f*(dp[16+3] + dp[16+2])
+                       -0.3333333333f * dp[24+2]);
     }
   } else {
-    for (i=start, yiq=it->yiq+start; i<end; i++, yiq++) {
-      yiq->i = yiq->q = 0.0;
+    for (i=start, yiq=it_yiq+start; i<end; i++, yiq++) {
+      yiq->i = yiq->q = 0.0f;
     }
   }
 }
@@ -902,37 +1014,37 @@ analogtv_sync(analogtv *it)
   int cur_vsync=it->cur_vsync;
   int lineno = 0;
   int i,j;
-  double osc,filt;
-  double *sp;
-  double cbfc=1.0/128.0;
+  float osc,filt;
+  float *sp;
+  float cbfc=1.0f/128.0f;
 
 /*  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;
+    filt=0.0f;
     for (j=0; j<ANALOGTV_H; j+=ANALOGTV_H/16) {
       filt += sp[j];
     }
     filt *= it->agclevel;
 
-    osc = (double)(ANALOGTV_V+i)/(double)ANALOGTV_V;
+    osc = (float)(ANALOGTV_V+i)/(float)ANALOGTV_V;
 
-    if (osc >= 1.05+0.0002 * filt) break;
+    if (osc >= 1.05f+0.0002f * 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;
+      unsigned lineno2 = (lineno + cur_vsync + ANALOGTV_V)%ANALOGTV_V;
+      if (!lineno2) lineno2 = ANALOGTV_V;
+      sp = it->rx_signal + lineno2*ANALOGTV_H + cur_hsync;
       for (i=-8; i<8; i++) {
-        osc = (double)(ANALOGTV_H+i)/(double)ANALOGTV_H;
+        osc = (float)(ANALOGTV_H+i)/(float)ANALOGTV_H;
         filt=(sp[i-3]+sp[i-2]+sp[i-1]+sp[i]) * it->agclevel;
 
-        if (osc >= 1.005 + 0.0001*filt) break;
+        if (osc >= 1.005f + 0.0001f*filt) break;
       }
       cur_hsync = (cur_hsync + i + ANALOGTV_H) % ANALOGTV_H;
     }
@@ -949,19 +1061,19 @@ analogtv_sync(analogtv *it)
     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) +
+        it->cb_phase[i&3] = it->cb_phase[i&3]*(1.0f-cbfc) +
           sp[i]*it->agclevel*cbfc;
       }
     }
 
     {
-      double tot=0.1;
-      double cbgain;
+      float tot=0.1f;
+      float cbgain;
 
       for (i=0; i<4; i++) {
         tot += it->cb_phase[i]*it->cb_phase[i];
       }
-      cbgain = 32.0/sqrt(tot);
+      cbgain = 32.0f/sqrtf(tot);
 
       for (i=0; i<4; i++) {
         it->line_cb_phase[lineno][i]=it->cb_phase[i]*cbgain;
@@ -983,14 +1095,14 @@ analogtv_sync(analogtv *it)
 }
 
 static double
-analogtv_levelmult(analogtv *it, int level)
+analogtv_levelmult(const analogtv *it, int level)
 {
   static const 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)
+analogtv_level(const analogtv *it, int y, int ytop, int ybot)
 {
   int level;
   if (ybot-ytop>=7) {
@@ -1052,8 +1164,209 @@ analogtv_setup_levels(analogtv *it, double avgheight)
   }
 }
 
+static void rnd_combine(unsigned *a0, unsigned *c0, unsigned a1, unsigned c1)
+{
+  *a0 = (*a0 * a1) & 0xffffffffu;
+  *c0 = (c1 + a1 * *c0) & 0xffffffffu;
+}
+
+static void rnd_seek_ac(unsigned *a, unsigned *c, unsigned dist)
+{
+  unsigned int a1 = *a, c1 = *c;
+  *a = 1, *c = 0;
+
+  while(dist)
+  {
+    if(dist & 1)
+      rnd_combine(a, c, a1, c1);
+    dist >>= 1;
+    rnd_combine(&a1, &c1, a1, c1);
+  }
+}
+
+static unsigned int rnd_seek(unsigned a, unsigned c, unsigned rnd, unsigned dist)
+{
+  rnd_seek_ac(&a, &c, dist);
+  return a * rnd + c;
+}
+
+static void analogtv_init_signal(const analogtv *it, double noiselevel, unsigned start, unsigned end)
+{
+  float *ps=it->rx_signal + start;
+  float *pe=it->rx_signal + end;
+  float *p=ps;
+  unsigned int fastrnd=rnd_seek(FASTRND_A, FASTRND_C, it->random0, start);
+  float nm1,nm2;
+  float noisemul = sqrt(noiselevel*150)/(float)0x7fffffff;
+
+  nm1 = ((int)fastrnd-(int)0x7fffffff) * noisemul;
+  while (p != pe) {
+    nm2=nm1;
+    fastrnd = (fastrnd*FASTRND_A+FASTRND_C) & 0xffffffffu;
+    nm1 = ((int)fastrnd-(int)0x7fffffff) * noisemul;
+    *p++ = nm1*nm2;
+  }
+}
+
+static void analogtv_add_signal(const analogtv *it, const analogtv_reception *rec, unsigned start, unsigned end, int ec)
+{
+  analogtv_input *inp=rec->input;
+  float *ps=it->rx_signal + start;
+  float *pe=it->rx_signal + end;
+  float *p=ps;
+  signed char *ss=&inp->signal[0][0];
+  signed char *se=&inp->signal[0][0] + ANALOGTV_SIGNAL_LEN;
+  signed char *s=ss + ((start + (unsigned)rec->ofs) % ANALOGTV_SIGNAL_LEN);
+  signed char *s2;
+  int i;
+  float level=rec->level;
+  float hfloss=rec->hfloss;
+  unsigned int fastrnd=rnd_seek(FASTRND_A, FASTRND_C, it->random1, start);
+  float dp[5];
+
+  const float noise_decay = 0.99995f;
+  float noise_ampl = 1.3f * powf(noise_decay, start);
+
+  if (ec > end)
+    ec = end;
+
+  /* assert((se-ss)%4==0 && (se-s)%4==0); */
+
+  for (i = start; i < ec; i++) { /* Sometimes start > ec. */
+
+    /* 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
+    */
+
+    float sig0=(float)s[0];
+    float noise = ((int)fastrnd-(int)0x7fffffff) * (50.0f/(float)0x7fffffff);
+    fastrnd = (fastrnd*FASTRND_A+FASTRND_C) & 0xffffffffu;
+
+    p[0] += sig0 * level * (1.0f - noise_ampl) + noise * noise_ampl;
+
+    noise_ampl *= noise_decay;
+
+    p++;
+    s++;
+    if (s>=se) s=ss;
+  }
+
+  dp[0]=0.0;
+  s2 = s;
+  for (i=1; i<5; i++) {
+    s2 -= 4;
+    if (s2 < ss)
+      s2 += ANALOGTV_SIGNAL_LEN;
+    dp[i] = (float)((int)s2[0]+(int)s2[1]+(int)s2[2]+(int)s2[3]);
+  }
+
+  assert(p <= pe);
+  assert(!((pe - p) % 4));
+  while (p != pe) {
+    float sig0,sig1,sig2,sig3,sigr;
+
+    sig0=(float)s[0];
+    sig1=(float)s[1];
+    sig2=(float)s[2];
+    sig3=(float)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);
+  }
+
+  assert(p == pe);
+}
+
+static void analogtv_thread_add_signals(void *thread_raw)
+{
+  const analogtv_thread *thread = (analogtv_thread *)thread_raw;
+  const analogtv *it = thread->it;
+  unsigned i, j;
+  unsigned subtotal_end;
+  
+  unsigned start = thread->signal_start;
+  while(start != thread->signal_end)
+  {
+    float *p;
+    
+    /* Work on 8 KB blocks; these should fit in L1. */
+    /* (Though it doesn't seem to help much on my system.) */
+    unsigned end = start + 2048;
+    if(end > thread->signal_end)
+      end = thread->signal_end;
+
+    analogtv_init_signal (it, it->noiselevel, start, end);
+
+    for (i = 0; i != it->rec_count; ++i) {
+      analogtv_add_signal (it, it->recs[i], start, end,
+                           !i ? it->channel_change_cycles : 0);
+    }
+
+    assert (!(start % ANALOGTV_SUBTOTAL_LEN));
+    assert (!(end % ANALOGTV_SUBTOTAL_LEN));
+
+    p = it->rx_signal + start;
+    subtotal_end = end / ANALOGTV_SUBTOTAL_LEN;
+    for (i = start / ANALOGTV_SUBTOTAL_LEN; i != subtotal_end; ++i) {
+      float sum = p[0];
+      for (j = 1; j != ANALOGTV_SUBTOTAL_LEN; ++j)
+        sum += p[j];
+      it->signal_subtotals[i] = sum;
+      p += ANALOGTV_SUBTOTAL_LEN;
+    }
+    
+    start = end;
+  }
+}
+
+static int analogtv_get_line(const analogtv *it, int lineno, int *slineno,
+                             int *ytop, int *ybot, unsigned *signal_offset)
+{
+  *slineno=lineno-ANALOGTV_TOP;
+  *ytop=(int)((*slineno*it->useheight/ANALOGTV_VISLINES -
+                  it->useheight/2)*it->puheight) + it->useheight/2;
+  *ybot=(int)(((*slineno+1)*it->useheight/ANALOGTV_VISLINES -
+                  it->useheight/2)*it->puheight) + it->useheight/2;
+#if 0
+  int linesig=analogtv_line_signature(input,lineno)
+    + it->hashnoise_times[lineno];
+#endif
+  *signal_offset = ((lineno+it->cur_vsync+ANALOGTV_V) % ANALOGTV_V) * ANALOGTV_H +
+                    it->line_hsync[lineno];
+
+  if (*ytop==*ybot) return 0;
+  if (*ybot<0 || *ytop>it->useheight) return 0;
+  if (*ytop<0) *ytop=0;
+  if (*ybot>it->useheight) *ybot=it->useheight;
+
+  if (*ybot > *ytop+ANALOGTV_MAX_LINEHEIGHT) *ybot=*ytop+ANALOGTV_MAX_LINEHEIGHT;
+  return 1;
+}
+
 static void
-analogtv_blast_imagerow(analogtv *it,
+analogtv_blast_imagerow(const analogtv *it,
                         float *rgbf, float *rgbf_end,
                         int ytop, int ybot)
 {
@@ -1061,14 +1374,16 @@ analogtv_blast_imagerow(analogtv *it,
   float *rpf;
   char *level_copyfrom[3];
   int xrepl=it->xrepl;
+  unsigned lineheight = ybot - ytop;
+  if (lineheight > ANALOGTV_MAX_LINEHEIGHT) lineheight = ANALOGTV_MAX_LINEHEIGHT;
   for (i=0; i<3; i++) level_copyfrom[i]=NULL;
 
   for (y=ytop; y<ybot; y++) {
-    int level=it->leveltable[ybot-ytop][y-ytop].index;
-    double levelmult=it->leveltable[ybot-ytop][y-ytop].value;
-    char *rowdata;
+    char *rowdata=it->image->data + y*it->image->bytes_per_line;
+    unsigned line = y-ytop;
 
-    rowdata=it->image->data + y*it->image->bytes_per_line;
+    int level=it->leveltable[lineheight][line].index;
+    float levelmult=it->leveltable[lineheight][line].value;
 
     /* 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
@@ -1118,7 +1433,7 @@ analogtv_blast_imagerow(analogtv *it,
                float_extraction_works &&
                it->image->byte_order==localbyteorder) {
         unsigned short *pixelptr=(unsigned short *)rowdata;
-        double r2,g2,b2;
+        float r2,g2,b2;
         float_extract_t r1,g1,b1;
         unsigned short pix;
 
@@ -1180,176 +1495,78 @@ analogtv_blast_imagerow(analogtv *it,
   }
 }
 
-void
-analogtv_draw(analogtv *it)
+static void analogtv_thread_draw_lines(void *thread_raw)
 {
-  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;
+  const analogtv_thread *thread = (analogtv_thread *)thread_raw;
+  const analogtv *it = thread->it;
 
-  float *raw_rgb_start=(float *)calloc(it->subwidth*3, sizeof(float));
-  float *raw_rgb_end=raw_rgb_start+3*it->subwidth;
-  float *rrp;
+  int lineno;
 
-  if (! raw_rgb_start) return;
-  analogtv_setup_frame(it);
-  analogtv_set_demod(it);
+  float *raw_rgb_start;
+  float *raw_rgb_end;
+  raw_rgb_start=(float *)calloc(it->subwidth*3, sizeof(float));
 
-  /* 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));
+  if (! raw_rgb_start) return;
 
-  analogtv_setup_levels(it, puheight * (double)it->useheight/(double)ANALOGTV_VISLINES);
+  raw_rgb_end=raw_rgb_start+3*it->subwidth;
 
-  overall_top=it->useheight;
-  overall_bot=0;
+  for (lineno=ANALOGTV_TOP + thread->thread_id;
+       lineno<ANALOGTV_BOT;
+       lineno += it->threads.count) {
+    int i,j,x,y;
 
-  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]);
+    int slineno, ytop, ybot;
+    unsigned signal_offset;
 
-    if (ytop==ybot) continue;
-    if (ybot<0 || ytop>it->useheight) continue;
-    if (ytop<0) ytop=0;
-    if (ybot>it->useheight) ybot=it->useheight;
+    const float *signal;
 
-    if (ybot > ytop+ANALOGTV_MAX_LINEHEIGHT) ybot=ytop+ANALOGTV_MAX_LINEHEIGHT;
+    int scanstart_i,scanend_i,squishright_i,squishdiv,pixrate;
+    float *rgb_start, *rgb_end;
+    float pixbright;
+    int pixmultinc;
 
-    if (ytop < overall_top) overall_top=ytop;
-    if (ybot > overall_bot) overall_bot=ybot;
+    float *rrp;
 
-    if (lineno==it->shrinkpulse) {
-      baseload += 0.4;
-      /*bigloadchange=1;*/
-      it->shrinkpulse=-1;
-    }
+    struct analogtv_yiq_s yiq[ANALOGTV_PIC_LEN+10];
 
-#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])) {
+    if (! analogtv_get_line(it, lineno, &slineno, &ytop, &ybot,
+        &signal_offset))
       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).
-    */
+    signal = it->rx_signal + signal_offset;
 
     {
-      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;
+      float bloomthisrow,shiftthisrow;
+      float viswidth,middle;
+      float 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;
+      bloomthisrow = -10.0f * it->crtload[lineno];
+      if (bloomthisrow<-10.0f) bloomthisrow=-10.0f;
+      if (bloomthisrow>2.0f) bloomthisrow=2.0f;
       if (slineno<16) {
-        shiftthisrow=it->horiz_desync * (exp(-0.17*slineno) *
-                                         (0.7+cos(slineno*0.6)));
+        shiftthisrow=it->horiz_desync * (expf(-0.17f*slineno) *
+                                         (0.7f+cosf(slineno*0.6f)));
       } else {
-        shiftthisrow=0.0;
+        shiftthisrow=0.0f;
       }
 
-      viswidth=ANALOGTV_PIC_LEN * 0.79 - 5.0*bloomthisrow;
+      viswidth=ANALOGTV_PIC_LEN * 0.79f - 5.0f*bloomthisrow;
       middle=ANALOGTV_PIC_LEN/2 - shiftthisrow;
 
-      scanwidth=it->width_control * puramp(it, 0.5, 0.3, 1.0);
+      scanwidth=it->width_control * puramp(it, 0.5f, 0.3f, 1.0f);
 
       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);
+      pixrate=(int)((viswidth*65536.0f*1.0f)/it->subwidth)/scanwidth;
+      scanstart_i=(int)((middle-viswidth*0.5f)*65536.0f);
       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);
+      squishright_i=(int)((middle+viswidth*(0.25f + 0.25f*puramp(it, 2.0f, 0.0f, 1.1f)
+                                            - it->squish_control)) *65536.0f);
       squishdiv=it->subwidth/15;
 
       rgb_start=raw_rgb_start+scl*3;
@@ -1360,9 +1577,9 @@ analogtv_draw(analogtv *it)
 #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,
+                    scanstart_i/65536.0f,
+                    squishright_i/65536.0f,
+                    scanend_i/65536.0f,
                     scl,scr,scw);
 #endif
     }
@@ -1370,14 +1587,13 @@ analogtv_draw(analogtv *it)
     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;
+        float levelmult=analogtv_levelmult(it, level);
+        float levelmult_y = levelmult * it->contrast_control
+          * puramp(it, 1.0f, 0.0f, 1.0f) / (0.5f+0.5f*it->puheight) * 0.070f;
+        float levelmult_iq = levelmult * 0.090f;
 
-        struct analogtv_yiq_s *yiq=it->yiq;
         analogtv_ntsc_to_yiq(it, lineno, signal,
-                             (scanstart_i>>16)-10, (scanend_i>>16)+10);
+                             (scanstart_i>>16)-10, (scanend_i>>16)+10, yiq);
         pixmultinc=pixrate;
 
         x=0;
@@ -1389,21 +1605,21 @@ analogtv_draw(analogtv *it)
         }
 
         while (i<scanend_i && x<it->usewidth) {
-          double pixfrac=(i&0xffff)/65536.0;
-          double invpixfrac=(1.0-pixfrac);
+          float pixfrac=(i&0xffff)/65536.0f;
+          float invpixfrac=(1.0f-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;
+          float interpy=(yiq[pati].y*invpixfrac
+                         + yiq[pati+1].y*pixfrac) * levelmult_y;
+          float interpi=(yiq[pati].i*invpixfrac
+                         + yiq[pati+1].i*pixfrac) * levelmult_iq;
+          float 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);
+          ili = (int)((interpi+0.5f) * it->cmap_i_levels);
+          qli = (int)((interpq+0.5f) * 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;
@@ -1439,12 +1655,11 @@ analogtv_draw(analogtv *it)
       }
     }
     else {
-      struct analogtv_yiq_s *yiq=it->yiq;
       analogtv_ntsc_to_yiq(it, lineno, signal,
-                           (scanstart_i>>16)-10, (scanend_i>>16)+10);
+                           (scanstart_i>>16)-10, (scanend_i>>16)+10, yiq);
 
-      pixbright=it->contrast_control * puramp(it, 1.0, 0.0, 1.0)
-        / (0.5+0.5*puheight) * 1024.0/100.0;
+      pixbright=it->contrast_control * puramp(it, 1.0f, 0.0f, 1.0f)
+        / (0.5f+0.5f*it->puheight) * 1024.0f/100.0f;
       pixmultinc=pixrate;
       i=scanstart_i; rrp=rgb_start;
       while (i<0 && rrp!=rgb_end) {
@@ -1453,14 +1668,14 @@ analogtv_draw(analogtv *it)
         rrp+=3;
       }
       while (i<scanend_i && rrp!=rgb_end) {
-        double pixfrac=(i&0xffff)/65536.0;
-        double invpixfrac=1.0-pixfrac;
+        float pixfrac=(i&0xffff)/65536.0f;
+        float invpixfrac=1.0f-pixfrac;
         int pati=i>>16;
-        double r,g,b;
+        float 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);
+        float interpy=(yiq[pati].y*invpixfrac + yiq[pati+1].y*pixfrac);
+        float interpi=(yiq[pati].i*invpixfrac + yiq[pati+1].i*pixfrac);
+        float interpq=(yiq[pati].q*invpixfrac + yiq[pati+1].q*pixfrac);
 
         /*
           According to the NTSC spec, Y,I,Q are generated as:
@@ -1478,12 +1693,12 @@ analogtv_draw(analogtv *it)
           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;
+        r=(interpy + 0.948f*interpi + 0.624f*interpq) * pixbright;
+        g=(interpy - 0.276f*interpi - 0.639f*interpq) * pixbright;
+        b=(interpy - 1.105f*interpi + 1.729f*interpq) * pixbright;
+        if (r<0.0f) r=0.0f;
+        if (g<0.0f) g=0.0f;
+        if (b<0.0f) b=0.0f;
         rrp[0]=r;
         rrp[1]=g;
         rrp[2]=b;
@@ -1496,7 +1711,7 @@ analogtv_draw(analogtv *it)
         rrp+=3;
       }
       while (rrp != rgb_end) {
-        rrp[0]=rrp[1]=rrp[2]=0.0;
+        rrp[0]=rrp[1]=rrp[2]=0.0f;
         rrp+=3;
       }
 
@@ -1504,7 +1719,178 @@ analogtv_draw(analogtv *it)
                               ytop,ybot);
     }
   }
+
   free(raw_rgb_start);
+}
+
+void
+analogtv_draw(analogtv *it, double noiselevel,
+              const analogtv_reception *const *recs, unsigned rec_count)
+{
+  int i,lineno;
+  int /*bigloadchange,*/drawcount;
+  double baseload;
+  int overall_top, overall_bot;
+
+  /* AnalogTV isn't very interesting if there isn't enough RAM. */
+  if (!it->image)
+    return;
+
+  it->rx_signal_level = noiselevel;
+  for (i = 0; i != rec_count; ++i) {
+    const analogtv_reception *rec = recs[i];
+    double level = rec->level;
+    analogtv_input *inp=rec->input;
+
+    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]))));
+
+    /* 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]));
+  }
+
+  analogtv_setup_frame(it);
+  analogtv_set_demod(it);
+
+  it->random0 = random();
+  it->random1 = random();
+  it->noiselevel = noiselevel;
+  it->recs = recs;
+  it->rec_count = rec_count;
+  threadpool_run(&it->threads, analogtv_thread_add_signals);
+  threadpool_wait(&it->threads);
+
+  it->channel_change_cycles=0;
+
+  /* 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]));
+
+  /* Repeat for signal_subtotals. */
+
+  memcpy(&it->signal_subtotals[ANALOGTV_SIGNAL_LEN / ANALOGTV_SUBTOTAL_LEN],
+         &it->signal_subtotals[0],
+         (2*ANALOGTV_H/ANALOGTV_SUBTOTAL_LEN)*sizeof(it->signal_subtotals[0]));
+
+  analogtv_sync(it); /* Requires the add_signals be complete. */
+
+  baseload=0.5;
+  /* if (it->hashnoise_on) baseload=0.5; */
+
+  /*bigloadchange=1;*/
+  drawcount=0;
+  it->crtload[ANALOGTV_TOP-1]=baseload;
+  it->puheight = puramp(it, 2.0, 1.0, 1.3) * it->height_control *
+    (1.125 - 0.125*puramp(it, 2.0, 2.0, 1.1));
+
+  analogtv_setup_levels(it, it->puheight * (double)it->useheight/(double)ANALOGTV_VISLINES);
+
+  for (lineno=ANALOGTV_TOP; lineno<ANALOGTV_BOT; lineno++) {
+    int slineno, ytop, ybot;
+    unsigned signal_offset;
+    if (! analogtv_get_line(it, lineno, &slineno, &ytop, &ybot, &signal_offset))
+      continue;
+
+    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).
+    */
+
+    {
+      /* This used to be an int, I suspect by mistake. - Dave */
+      float totsignal=0;
+      float ncl/*,diff*/;
+      unsigned frac;
+      size_t end0, end1;
+      const float *p;
+
+      frac = signal_offset & (ANALOGTV_SUBTOTAL_LEN - 1);
+      p = it->rx_signal + (signal_offset & ~(ANALOGTV_SUBTOTAL_LEN - 1));
+      for (i=0; i != frac; i++) {
+        totsignal -= p[i];
+      }
+
+      end0 = (signal_offset + ANALOGTV_PIC_LEN);
+
+      end1 = end0 / ANALOGTV_SUBTOTAL_LEN;
+      for (i=signal_offset / ANALOGTV_SUBTOTAL_LEN; i<end1; i++) {
+        totsignal += it->signal_subtotals[i];
+      }
+
+      frac = end0 & (ANALOGTV_SUBTOTAL_LEN - 1);
+      p = it->rx_signal + (end0 & ~(ANALOGTV_SUBTOTAL_LEN - 1));
+      for (i=0; i != frac; i++) {
+        totsignal += p[i];
+      }
+
+      totsignal *= it->agclevel;
+      ncl = 0.95f * it->crtload[lineno-1] +
+        0.05f*(baseload +
+               (totsignal-30000)/100000.0f +
+               (slineno>184 ? (slineno-184)*(lineno-184)*0.001f * it->squeezebottom
+                : 0.0f));
+      /*diff=ncl - it->crtload[lineno];*/
+      /*bigloadchange = (diff>0.01 || diff<-0.01);*/
+      it->crtload[lineno]=ncl;
+    }
+  }
+
+  threadpool_run(&it->threads, analogtv_thread_draw_lines);
+  threadpool_wait(&it->threads);
 
 #if 0
   /* poor attempt at visible retrace */
@@ -1528,6 +1914,18 @@ analogtv_draw(analogtv *it)
     it->need_clear=0;
   }
 
+  /*
+    Subtle change: overall_bot was the bottom of the last scan line. Now it's
+    the top of the next-after-the-last scan line. This is the same until
+    the y-dimension is > 2400, note ANALOGTV_MAX_LINEHEIGHT.
+  */
+
+  overall_top=(int)(it->useheight*(1-it->puheight)/2);
+  overall_bot=(int)(it->useheight*(1+it->puheight)/2);
+
+  if (overall_top<0) overall_top=0;
+  if (overall_bot>it->useheight) overall_bot=it->useheight;
+
   if (overall_top>0) {
     XClearArea(it->dpy, it->window,
                it->screen_xo, it->screen_yo,
@@ -1720,101 +2118,6 @@ void analogtv_channel_noise(analogtv_input *it, analogtv_input *s2)
 #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;
-  signed char *ss=&inp->signal[0][0];
-  signed char *se=&inp->signal[0][0] + ANALOGTV_SIGNAL_LEN;
-  signed 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]) {
@@ -1840,25 +2143,6 @@ void analogtv_add_signal(analogtv *it, analogtv_reception *rec)
 #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)
 {