1 /* analogtv, Copyright (c) 2003, 2004 Trevor Blackwell <tlb@tlb.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
14 This is the code for implementing something that looks like a conventional
15 analog TV set. It simulates the following characteristics of standard
18 - Realistic rendering of a composite video signal
19 - Compression & brightening on the right, as the scan gets truncated
20 because of saturation in the flyback transformer
21 - Blooming of the picture dependent on brightness
22 - Overscan, cutting off a few pixels on the left side.
23 - Colored text in mixed graphics/text modes
25 It's amazing how much it makes your high-end monitor look like at large
26 late-70s TV. All you need is to put a big "Solid State" logo in curly script
27 on it and you'd be set.
29 In DirectColor or TrueColor modes, it generates pixel values
30 directly from RGB values it calculates across each scan line. In
31 PseudoColor mode, it consider each possible pattern of 5 preceding
32 bit values in each possible position modulo 4 and allocates a color
33 for each. A few things, like the brightening on the right side as
34 the horizontal trace slows down, aren't done in PseudoColor.
36 I originally wrote it for the Apple ][ emulator, and generalized it
37 here for use with a rewrite of xteevee and possibly others.
39 A maxim of technology is that failures reveal underlying mechanism.
40 A good way to learn how something works is to push it to failure.
41 The way it fails will usually tell you a lot about how it works. The
42 corollary for this piece of software is that in order to emulate
43 realistic failures of a TV set, it has to work just like a TV set.
44 So there is lots of DSP-style emulation of analog circuitry for
45 things like color decoding, H and V sync following, and more. In
46 2003, computers are just fast enough to do this at television signal
47 rates. We use a 14 MHz sample rate here, so we can do on the order
48 of a couple hundred instructions per sample and keep a good frame
51 Trevor Blackwell <tlb@tlb.org>
56 #else /* !HAVE_COCOA */
57 # include <X11/Xlib.h>
58 # include <X11/Xutil.h>
63 #include "resources.h"
66 #include "grabscreen.h"
71 /* only works on linux + freebsd */
72 #include <machine/cpufunc.h>
74 #define DTIME_DECL u_int64_t dtimes[100]; int n_dtimes
75 #define DTIME_START do {n_dtimes=0; dtimes[n_dtimes++]=rdtsc(); } while (0)
76 #define DTIME dtimes[n_dtimes++]=rdtsc()
77 #define DTIME_SHOW(DIV) \
79 double _dtime_div=(DIV); \
80 printf("time/%.1f: ",_dtime_div); \
81 for (i=1; i<n_dtimes; i++) \
82 printf(" %0.9f",(dtimes[i]-dtimes[i-1])* 1e-9 / _dtime_div); \
89 #define DTIME_START do { } while (0)
90 #define DTIME do { } while (0)
91 #define DTIME_SHOW(DIV) do { } while (0)
96 #define FASTRND (fastrnd = fastrnd*1103515245+12345)
98 static void analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
101 static double puramp(analogtv *it, double tc, double start, double over)
103 double pt=it->powerup-start;
105 if (pt<0.0) return 0.0;
106 if (pt>900.0 || pt/tc>8.0) return 1.0;
108 ret=(1.0-exp(-pt/tc))*over;
109 if (ret>1.0) return 1.0;
114 There are actual standards for TV signals: NTSC and RS-170A describe the
115 system used in the US and Japan. Europe has slightly different systems, but
116 not different enough to make substantially different screensaver displays.
117 Sadly, the standards bodies don't do anything so useful as publish the spec on
118 the web. Best bets are:
120 http://www.ee.washington.edu/conselec/CE/kuhn/ntsc/95x4.htm
121 http://www.ntsc-tv.com/ntsc-index-02.htm
123 In DirectColor or TrueColor modes, it generates pixel values directly from RGB
124 values it calculates across each scan line. In PseudoColor mode, it consider
125 each possible pattern of 5 preceding bit values in each possible position
126 modulo 4 and allocates a color for each. A few things, like the brightening on
127 the right side as the horizontal trace slows down, aren't done in PseudoColor.
129 I'd like to add a bit of visible retrace, but it conflicts with being able to
130 bitcopy the image when fast scrolling. After another couple of CPU
131 generations, we could probably regenerate the whole image from scratch every
132 time. On a P4 2 GHz it can manage this fine for blinking text, but scrolling
136 /* localbyteorder is MSBFirst or LSBFirst */
137 static int localbyteorder;
138 static const double float_low8_ofs=8388608.0;
139 static int float_extraction_works;
151 unsigned int localbyteorder_loc = (MSBFirst<<24) | (LSBFirst<<0);
152 localbyteorder=*(char *)&localbyteorder_loc;
159 float_extraction_works=1;
160 for (i=0; i<256*4; i++) {
161 fe.f=float_low8_ofs+(double)i;
165 printf("Float extraction failed for %d => %d\n",i,ans);
167 float_extraction_works=0;
176 analogtv_set_defaults(analogtv *it, char *prefix)
180 sprintf(buf,"%sTVTint",prefix);
181 it->tint_control = get_float_resource(it->dpy, buf,"TVTint");
182 sprintf(buf,"%sTVColor",prefix);
183 it->color_control = get_float_resource(it->dpy, buf,"TVColor")/100.0;
184 sprintf(buf,"%sTVBrightness",prefix);
185 it->brightness_control = get_float_resource(it->dpy, buf,"TVBrightness") / 100.0;
186 sprintf(buf,"%sTVContrast",prefix);
187 it->contrast_control = get_float_resource(it->dpy, buf,"TVContrast") / 100.0;
188 it->height_control = 1.0;
189 it->width_control = 1.0;
190 it->squish_control = 0.0;
195 it->hashnoise_enable=1;
197 it->horiz_desync=frand(10.0)-5.0;
198 it->squeezebottom=frand(5.0)-1.0;
201 printf("analogtv: prefix=%s\n",prefix);
202 printf(" use: shm=%d cmap=%d color=%d\n",
203 it->use_shm,it->use_cmap,it->use_color);
204 printf(" controls: tint=%g color=%g brightness=%g contrast=%g\n",
205 it->tint_control, it->color_control, it->brightness_control,
206 it->contrast_control);
207 printf(" freq_error %g: %g %d\n",
208 it->freq_error, it->freq_error_inc, it->flutter_tint);
209 printf(" desync: %g %d\n",
210 it->horiz_desync, it->flutter_horiz_desync);
211 printf(" hashnoise rpm: %g\n",
213 printf(" vis: %d %d %d\n",
214 it->visclass, it->visbits, it->visdepth);
215 printf(" shift: %d-%d %d-%d %d-%d\n",
216 it->red_invprec,it->red_shift,
217 it->green_invprec,it->green_shift,
218 it->blue_invprec,it->blue_shift);
219 printf(" size: %d %d %d %d xrepl=%d\n",
220 it->usewidth, it->useheight,
221 it->screen_xo, it->screen_yo, it->xrepl);
223 printf(" ANALOGTV_V=%d\n",ANALOGTV_V);
224 printf(" ANALOGTV_TOP=%d\n",ANALOGTV_TOP);
225 printf(" ANALOGTV_VISLINES=%d\n",ANALOGTV_VISLINES);
226 printf(" ANALOGTV_BOT=%d\n",ANALOGTV_BOT);
227 printf(" ANALOGTV_H=%d\n",ANALOGTV_H);
228 printf(" ANALOGTV_SYNC_START=%d\n",ANALOGTV_SYNC_START);
229 printf(" ANALOGTV_BP_START=%d\n",ANALOGTV_BP_START);
230 printf(" ANALOGTV_CB_START=%d\n",ANALOGTV_CB_START);
231 printf(" ANALOGTV_PIC_START=%d\n",ANALOGTV_PIC_START);
232 printf(" ANALOGTV_PIC_LEN=%d\n",ANALOGTV_PIC_LEN);
233 printf(" ANALOGTV_FP_START=%d\n",ANALOGTV_FP_START);
234 printf(" ANALOGTV_PIC_END=%d\n",ANALOGTV_PIC_END);
235 printf(" ANALOGTV_HASHNOISE_LEN=%d\n",ANALOGTV_HASHNOISE_LEN);
241 extern Bool mono_p; /* shoot me */
244 analogtv_free_image(analogtv *it)
248 #ifdef HAVE_XSHM_EXTENSION
249 destroy_xshm_image(it->dpy, it->image, &it->shm_info);
252 XDestroyImage(it->image);
259 analogtv_alloc_image(analogtv *it)
262 #ifdef HAVE_XSHM_EXTENSION
263 it->image=create_xshm_image(it->dpy, it->xgwa.visual, it->xgwa.depth, ZPixmap, 0,
264 &it->shm_info, it->usewidth, it->useheight);
266 if (!it->image) it->use_shm=0;
269 it->image = XCreateImage(it->dpy, it->xgwa.visual, it->xgwa.depth, ZPixmap, 0, 0,
270 it->usewidth, it->useheight, 8, 0);
271 it->image->data = (char *)malloc(it->image->height * it->image->bytes_per_line);
273 memset (it->image->data, 0, it->image->height * it->image->bytes_per_line);
278 analogtv_configure(analogtv *it)
280 int oldwidth=it->usewidth;
281 int oldheight=it->useheight;
282 int wlim,hlim,height_diff;
284 /* If the window is very small, don't let the image we draw get lower
285 than the actual TV resolution (266x200.)
287 If the aspect ratio of the window is within 15% of a 4:3 ratio,
288 then scale the image to exactly fill the window.
290 Otherwise, center the image either horizontally or vertically,
291 padding on the left+right, or top+bottom, but not both.
293 If it's very close (2.5%) to a multiple of VISLINES, make it exact
294 For example, it maps 1024 => 1000.
296 float percent = 0.15; /* jwz: 20% caused severe top/bottom clipping
297 in Pong on 1680x1050 iMac screen. */
298 float min_ratio = 4.0 / 3.0 * (1 - percent);
299 float max_ratio = 4.0 / 3.0 * (1 + percent);
301 float height_snap=0.025;
303 hlim = it->xgwa.height;
304 wlim = it->xgwa.width;
305 ratio = wlim / (float) hlim;
308 /* Fill the whole iPhone screen, even though that distorts the image. */
309 min_ratio = 320.0 / 480.0 * (1 - percent);
310 max_ratio = 480.0 / 320.0 * (1 + percent);
313 if (wlim < 266 || hlim < 200)
319 "size: minimal: %dx%d in %dx%d (%.3f < %.3f < %.3f)\n",
320 wlim, hlim, it->xgwa.width, it->xgwa.height,
321 min_ratio, ratio, max_ratio);
324 else if (ratio > min_ratio && ratio < max_ratio)
328 "size: close enough: %dx%d (%.3f < %.3f < %.3f)\n",
329 wlim, hlim, min_ratio, ratio, max_ratio);
332 else if (ratio > max_ratio)
334 wlim = hlim*max_ratio;
337 "size: center H: %dx%d in %dx%d (%.3f < %.3f < %.3f)\n",
338 wlim, hlim, it->xgwa.width, it->xgwa.height,
339 min_ratio, ratio, max_ratio);
342 else /* ratio < min_ratio */
344 hlim = wlim/min_ratio;
347 "size: center V: %dx%d in %dx%d (%.3f < %.3f < %.3f)\n",
348 wlim, hlim, it->xgwa.width, it->xgwa.height,
349 min_ratio, ratio, max_ratio);
354 height_diff = ((hlim + ANALOGTV_VISLINES/2) % ANALOGTV_VISLINES) - ANALOGTV_VISLINES/2;
355 if (height_diff != 0 && fabs(height_diff) < hlim * height_snap)
361 /* Most times this doesn't change */
362 if (wlim != oldwidth || hlim != oldheight) {
367 it->xrepl=1+it->usewidth/640;
368 if (it->xrepl>2) it->xrepl=2;
369 it->subwidth=it->usewidth/it->xrepl;
371 analogtv_free_image(it);
372 analogtv_alloc_image(it);
375 it->screen_xo = (it->xgwa.width-it->usewidth)/2;
376 it->screen_yo = (it->xgwa.height-it->useheight)/2;
381 analogtv_reconfigure(analogtv *it)
383 XGetWindowAttributes (it->dpy, it->window, &it->xgwa);
384 analogtv_configure(it);
388 analogtv_allocate(Display *dpy, Window window)
396 it=(analogtv *)calloc(1,sizeof(analogtv));
405 #ifdef HAVE_XSHM_EXTENSION
411 XGetWindowAttributes (it->dpy, it->window, &it->xgwa);
413 it->screen=it->xgwa.screen;
414 it->colormap=it->xgwa.colormap;
415 it->visclass=it->xgwa.visual->class;
416 it->visbits=it->xgwa.visual->bits_per_rgb;
417 it->visdepth=it->xgwa.depth;
418 if (it->visclass == TrueColor || it->visclass == DirectColor) {
419 if (get_integer_resource (it->dpy, "use_cmap", "Integer")) {
424 it->use_color=!mono_p;
426 else if (it->visclass == PseudoColor || it->visclass == StaticColor) {
428 it->use_color=!mono_p;
435 it->red_mask=it->xgwa.visual->red_mask;
436 it->green_mask=it->xgwa.visual->green_mask;
437 it->blue_mask=it->xgwa.visual->blue_mask;
438 it->red_shift=it->red_invprec=-1;
439 it->green_shift=it->green_invprec=-1;
440 it->blue_shift=it->blue_invprec=-1;
442 /* Is there a standard way to do this? Does this handle all cases? */
444 for (shift=0; shift<32; shift++) {
445 for (prec=1; prec<16 && prec<40-shift; prec++) {
446 unsigned long mask=(0xffffUL>>(16-prec)) << shift;
447 if (it->red_shift<0 && mask==it->red_mask)
448 it->red_shift=shift, it->red_invprec=16-prec;
449 if (it->green_shift<0 && mask==it->green_mask)
450 it->green_shift=shift, it->green_invprec=16-prec;
451 if (it->blue_shift<0 && mask==it->blue_mask)
452 it->blue_shift=shift, it->blue_invprec=16-prec;
455 if (it->red_shift<0 || it->green_shift<0 || it->blue_shift<0) {
456 if (0) fprintf(stderr,"Can't figure out color space\n");
460 for (i=0; i<ANALOGTV_CV_MAX; i++) {
461 int intensity=pow(i/256.0, 0.8)*65535.0; /* gamma correction */
462 if (intensity>65535) intensity=65535;
463 it->red_values[i]=((intensity>>it->red_invprec)<<it->red_shift);
464 it->green_values[i]=((intensity>>it->green_invprec)<<it->green_shift);
465 it->blue_values[i]=((intensity>>it->blue_invprec)<<it->blue_shift);
470 gcv.background=get_pixel_resource(it->dpy, it->colormap,
471 "background", "Background");
473 it->gc = XCreateGC(it->dpy, it->window, GCBackground, &gcv);
474 XSetWindowBackground(it->dpy, it->window, gcv.background);
475 XClearWindow(dpy,window);
477 analogtv_configure(it);
487 analogtv_release(analogtv *it)
491 #ifdef HAVE_XSHM_EXTENSION
492 destroy_xshm_image(it->dpy, it->image, &it->shm_info);
495 XDestroyImage(it->image);
499 if (it->gc) XFreeGC(it->dpy, it->gc);
501 if (it->n_colors) XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L);
508 First generate the I and Q reference signals, which we'll multiply
509 the input signal by to accomplish the demodulation. Normally they
510 are shifted 33 degrees from the colorburst. I think this was convenient
511 for inductor-capacitor-vacuum tube implementation.
513 The tint control, FWIW, just adds a phase shift to the chroma signal,
514 and the color control controls the amplitude.
516 In text modes (colormode==0) the system disabled the color burst, and no
517 color was detected by the monitor.
519 freq_error gives a mismatch between the built-in oscillator and the
520 TV's colorbust. Some II Plus machines seemed to occasionally get
521 instability problems -- the crystal oscillator was a single
522 transistor if I remember correctly -- and the frequency would vary
523 enough that the tint would change across the width of the screen.
524 The left side would be in correct tint because it had just gotten
525 resynchronized with the color burst.
527 If we're using a colormap, set it up.
530 analogtv_set_demod(analogtv *it)
532 int y_levels=10,i_levels=5,q_levels=5;
535 In principle, we might be able to figure out how to adjust the
536 color map frame-by-frame to get some nice color bummage. But I'm
537 terrified of changing the color map because we'll get flashing.
539 I can hardly believe we still have to deal with colormaps. They're
540 like having NEAR PTRs: an enormous hassle for the programmer just
541 to save on memory. They should have been deprecated by 1995 or
545 if (it->use_cmap && !it->n_colors) {
548 XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L);
554 for (yli=0; yli<y_levels; yli++) {
555 for (ili=0; ili<i_levels; ili++) {
556 for (qli=0; qli<q_levels; qli++) {
557 double interpy,interpi,interpq;
558 double levelmult=700.0;
562 interpy=100.0 * ((double)yli/y_levels);
563 interpi=50.0 * (((double)ili-(0.5*i_levels))/(double)i_levels);
564 interpq=50.0 * (((double)qli-(0.5*q_levels))/(double)q_levels);
566 r=(int)((interpy + 1.04*interpi + 0.624*interpq)*levelmult);
567 g=(int)((interpy - 0.276*interpi - 0.639*interpq)*levelmult);
568 b=(int)((interpy - 1.105*interpi + 1.729*interpq)*levelmult);
570 if (r>65535) r=65535;
572 if (g>65535) g=65535;
574 if (b>65535) b=65535;
577 printf("%0.2f %0.2f %0.2f => %02x%02x%02x\n",
578 interpy, interpi, interpq,
586 if (!XAllocColor(it->dpy, it->colormap, &col)) {
587 if (q_levels > y_levels*4/12)
589 else if (i_levels > y_levels*5/12)
598 it->colors[it->n_colors++]=col.pixel;
603 it->cmap_y_levels=y_levels;
604 it->cmap_i_levels=i_levels;
605 it->cmap_q_levels=q_levels;
614 analogtv_line_signature(analogtv_input *input, int lineno)
617 char *origsignal=&input->signal[(lineno+input->vsync)
618 %ANALOGTV_V][input->line_hsync[lineno]];
622 for (i=0; i<ANALOGTV_PIC_LEN; i++) {
624 hash = hash + (hash<<17) + c;
627 hash += input->line_hsync[lineno];
630 hash += input->hashnoise_times[lineno];
639 /* Here we model the analog circuitry of an NTSC television.
640 Basically, it splits the signal into 3 signals: Y, I and Q. Y
641 corresponds to luminance, and you get it by low-pass filtering the
642 input signal to below 3.57 MHz.
644 I and Q are the in-phase and quadrature components of the 3.57 MHz
645 subcarrier. We get them by multiplying by cos(3.57 MHz*t) and
646 sin(3.57 MHz*t), and low-pass filtering. Because the eye has less
647 resolution in some colors than others, the I component gets
648 low-pass filtered at 1.5 MHz and the Q at 0.5 MHz. The I component
649 is approximately orange-blue, and Q is roughly purple-green. See
650 http://www.ntsc-tv.com for details.
652 We actually do an awful lot to the signal here. I suspect it would
653 make sense to wrap them all up together by calculating impulse
654 response and doing FFT convolutions.
659 analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
665 int phasecorr=(signal-it->rx_signal)&3;
666 struct analogtv_yiq_s *yiq;
668 double agclevel=it->agclevel;
669 double brightadd=it->brightness_control*100.0 - ANALOGTV_BLACK_LEVEL;
670 double delay[MAXDELAY+ANALOGTV_PIC_LEN], *dp;
675 double cb_i=(it->line_cb_phase[lineno][(2+phasecorr)&3]-
676 it->line_cb_phase[lineno][(0+phasecorr)&3])/16.0;
677 double cb_q=(it->line_cb_phase[lineno][(3+phasecorr)&3]-
678 it->line_cb_phase[lineno][(1+phasecorr)&3])/16.0;
680 colormode = (cb_i * cb_i + cb_q * cb_q) > 2.8;
683 double tint_i = -cos((103 + it->color_control)*3.1415926/180);
684 double tint_q = sin((103 + it->color_control)*3.1415926/180);
686 multiq2[0] = (cb_i*tint_i - cb_q*tint_q) * it->color_control;
687 multiq2[1] = (cb_q*tint_i + cb_i*tint_q) * it->color_control;
688 multiq2[2]=-multiq2[0];
689 multiq2[3]=-multiq2[1];
695 printf("multiq = [%0.3f %0.3f %0.3f %0.3f] ",
696 it->multiq[60],it->multiq[61],it->multiq[62],it->multiq[63]);
697 printf("it->line_cb_phase = [%0.3f %0.3f %0.3f %0.3f]\n",
698 it->line_cb_phase[lineno][0],it->line_cb_phase[lineno][1],
699 it->line_cb_phase[lineno][2],it->line_cb_phase[lineno][3]);
700 printf("multiq2 = [%0.3f %0.3f %0.3f %0.3f]\n",
701 multiq2[0],multiq2[1],multiq2[2],multiq2[3]);
705 dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
706 for (i=0; i<5; i++) dp[i]=0.0;
709 assert(end < ANALOGTV_PIC_LEN+10);
711 dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
712 for (i=0; i<24; i++) dp[i]=0.0;
713 for (i=start, yiq=it->yiq+start, sp=signal+start;
715 i++, dp--, yiq++, sp++) {
717 /* Now filter them. These are infinite impulse response filters
718 calculated by the script at
719 http://www-users.cs.york.ac.uk/~fisher/mkfilter. This is
720 fixed-point integer DSP, son. No place for wimps. We do it in
721 integer because you can count on integer being faster on most
722 CPUs. We care about speed because we need to recalculate every
723 time we blink text, and when we spew random bytes into screen
724 memory. This is roughly 16.16 fixed point arithmetic, but we
725 scale some filter values up by a few bits to avoid some nasty
728 /* Filter Y with a 4-pole low-pass Butterworth filter at 3.5 MHz
729 with an extra zero at 3.5 MHz, from
730 mkfilter -Bu -Lp -o 4 -a 2.1428571429e-01 0 -Z 2.5e-01 -l
733 dp[0] = sp[0] * 0.0469904257251935 * agclevel;
734 dp[8] = (+1.0*(dp[6]+dp[0])
740 yiq->y = dp[8] + brightadd;
744 dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
745 for (i=0; i<27; i++) dp[i]=0.0;
747 for (i=start, yiq=it->yiq+start, sp=signal+start;
749 i++, dp--, yiq++, sp++) {
752 /* Filter I and Q with a 3-pole low-pass Butterworth filter at
753 1.5 MHz with an extra zero at 3.5 MHz, from
754 mkfilter -Bu -Lp -o 3 -a 1.0714285714e-01 0 -Z 2.5000000000e-01 -l
758 dp[0] = sig*multiq2[i&3] * 0.0833333333333;
759 yiq->i=dp[8] = (dp[5] + dp[0]
762 -0.3333333333 * dp[10]);
764 dp[16] = sig*multiq2[(i+3)&3] * 0.0833333333333;
765 yiq->q=dp[24] = (dp[16+5] + dp[16+0]
766 +3.0*(dp[16+4] + dp[16+1])
767 +4.0*(dp[16+3] + dp[16+2])
768 -0.3333333333 * dp[24+2]);
771 for (i=start, yiq=it->yiq+start; i<end; i++, yiq++) {
772 yiq->i = yiq->q = 0.0;
778 analogtv_setup_teletext(analogtv_input *input)
781 int teletext=ANALOGTV_BLACK_LEVEL;
783 /* Teletext goes in line 21. But I suspect there are other things
784 in the vertical retrace interval */
786 for (y=19; y<22; y++) {
787 for (x=ANALOGTV_PIC_START; x<ANALOGTV_PIC_END; x++) {
789 teletext=(random()&1) ? ANALOGTV_WHITE_LEVEL : ANALOGTV_BLACK_LEVEL;
791 input->signal[y][x]=teletext;
797 analogtv_setup_frame(analogtv *it)
803 if (it->flutter_horiz_desync) {
804 /* Horizontal sync during vertical sync instability. */
805 it->horiz_desync += -0.10*(it->horiz_desync-3.0) +
806 ((int)(random()&0xff)-0x80) *
807 ((int)(random()&0xff)-0x80) *
808 ((int)(random()&0xff)-0x80) * 0.000001;
811 for (i=0; i<ANALOGTV_V; i++) {
812 it->hashnoise_times[i]=0;
815 if (it->hashnoise_enable && !it->hashnoise_on) {
816 if (random()%10000==0) {
818 it->shrinkpulse=random()%ANALOGTV_V;
821 if (random()%1000==0) {
824 if (it->hashnoise_on) {
825 it->hashnoise_rpm += (15000.0 - it->hashnoise_rpm)*0.05 +
826 ((int)(random()%2000)-1000)*0.1;
828 it->hashnoise_rpm -= 100 + 0.01*it->hashnoise_rpm;
829 if (it->hashnoise_rpm<0.0) it->hashnoise_rpm=0.0;
831 if (it->hashnoise_rpm > 0.0) {
833 int hnc=it->hashnoise_counter; /* in 24.8 format */
835 /* Convert rpm of a 16-pole motor into dots in 24.8 format */
836 hni = (int)(ANALOGTV_V * ANALOGTV_H * 256.0 /
837 (it->hashnoise_rpm * 16.0 / 60.0 / 60.0));
839 while (hnc < (ANALOGTV_V * ANALOGTV_H)<<8) {
840 y=(hnc>>8)/ANALOGTV_H;
841 x=(hnc>>8)%ANALOGTV_H;
843 if (x>0 && x<ANALOGTV_H - ANALOGTV_HASHNOISE_LEN) {
844 it->hashnoise_times[y]=x;
846 hnc += hni + (int)(random()%65536)-32768;
848 /* hnc -= (ANALOGTV_V * ANALOGTV_H)<<8;*/
851 if (it->rx_signal_level != 0.0)
852 it->agclevel = 1.0/it->rx_signal_level;
857 for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
858 printf(" %0.3f",it->ghostfir[i]);
860 printf(" siglevel=%g agc=%g\n", siglevel, it->agclevel);
865 analogtv_setup_sync(analogtv_input *input, int do_cb, int do_ssavi)
870 int synclevel = do_ssavi ? ANALOGTV_WHITE_LEVEL : ANALOGTV_SYNC_LEVEL;
872 for (lineno=0; lineno<ANALOGTV_V; lineno++) {
873 vsync=lineno>=3 && lineno<7;
875 sig=input->signal[lineno];
877 i=ANALOGTV_SYNC_START;
879 while (i<ANALOGTV_BP_START) sig[i++]=ANALOGTV_BLANK_LEVEL;
880 while (i<ANALOGTV_H) sig[i++]=synclevel;
882 while (i<ANALOGTV_BP_START) sig[i++]=synclevel;
883 while (i<ANALOGTV_PIC_START) sig[i++]=ANALOGTV_BLANK_LEVEL;
884 while (i<ANALOGTV_FP_START) sig[i++]=ANALOGTV_BLACK_LEVEL;
886 while (i<ANALOGTV_H) sig[i++]=ANALOGTV_BLANK_LEVEL;
889 /* 9 cycles of colorburst */
890 for (i=ANALOGTV_CB_START; i<ANALOGTV_CB_START+36; i+=4) {
891 sig[i+1] += ANALOGTV_CB_LEVEL;
892 sig[i+3] -= ANALOGTV_CB_LEVEL;
899 analogtv_sync(analogtv *it)
901 int cur_hsync=it->cur_hsync;
902 int cur_vsync=it->cur_vsync;
907 double cbfc=1.0/128.0;
909 /* sp = it->rx_signal + lineno*ANALOGTV_H + cur_hsync;*/
910 for (i=-32; i<32; i++) {
911 lineno = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V;
912 sp = it->rx_signal + lineno*ANALOGTV_H;
914 for (j=0; j<ANALOGTV_H; j+=ANALOGTV_H/16) {
917 filt *= it->agclevel;
919 osc = (double)(ANALOGTV_V+i)/(double)ANALOGTV_V;
921 if (osc >= 1.05+0.0002 * filt) break;
923 cur_vsync = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V;
925 for (lineno=0; lineno<ANALOGTV_V; lineno++) {
927 if (lineno>5 && lineno<ANALOGTV_V-3) { /* ignore vsync interval */
929 sp = it->rx_signal + ((lineno + cur_vsync + ANALOGTV_V)%ANALOGTV_V
930 )*ANALOGTV_H + cur_hsync;
931 for (i=-8; i<8; i++) {
932 osc = (double)(ANALOGTV_H+i)/(double)ANALOGTV_H;
933 filt=(sp[i-3]+sp[i-2]+sp[i-1]+sp[i]) * it->agclevel;
935 if (osc >= 1.005 + 0.0001*filt) break;
937 cur_hsync = (cur_hsync + i + ANALOGTV_H) % ANALOGTV_H;
940 it->line_hsync[lineno]=(cur_hsync + ANALOGTV_PIC_START +
941 ANALOGTV_H) % ANALOGTV_H;
943 /* Now look for the colorburst, which is a few cycles after the H
944 sync pulse, and store its phase.
945 The colorburst is 9 cycles long, and we look at the middle 5
950 sp = it->rx_signal + lineno*ANALOGTV_H + (cur_hsync&~3);
951 for (i=ANALOGTV_CB_START+8; i<ANALOGTV_CB_START+36-8; i++) {
952 it->cb_phase[i&3] = it->cb_phase[i&3]*(1.0-cbfc) +
953 sp[i]*it->agclevel*cbfc;
961 for (i=0; i<4; i++) {
962 tot += it->cb_phase[i]*it->cb_phase[i];
964 cbgain = 32.0/sqrt(tot);
966 for (i=0; i<4; i++) {
967 it->line_cb_phase[lineno][i]=it->cb_phase[i]*cbgain;
972 if (0) printf("hs=%d cb=[%0.3f %0.3f %0.3f %0.3f]\n",
974 it->cb_phase[0], it->cb_phase[1],
975 it->cb_phase[2], it->cb_phase[3]);
978 /* if (random()%2000==0) cur_hsync=random()%ANALOGTV_H; */
981 it->cur_hsync = cur_hsync;
982 it->cur_vsync = cur_vsync;
986 analogtv_levelmult(analogtv *it, int level)
988 static const double levelfac[3]={-7.5, 5.5, 24.5};
989 return (40.0 + levelfac[level]*puramp(it, 3.0, 6.0, 1.0))/256.0;
993 analogtv_level(analogtv *it, int y, int ytop, int ybot)
997 if (y==ytop || y==ybot-1) level=0;
998 else if (y==ytop+1 || y==ybot-2) level=1;
1001 else if (ybot-ytop>=5) {
1002 if (y==ytop || y==ybot-1) level=0;
1005 else if (ybot-ytop>=3) {
1006 if (y==ytop) level=0;
1016 The point of this stuff is to ensure that when useheight is not a
1017 multiple of VISLINES so that TV scan lines map to different numbers
1018 of vertical screen pixels, the total brightness of each scan line
1020 ANALOGTV_MAX_LINEHEIGHT corresponds to 2400 vertical pixels, beyond which
1021 it interpolates extra black lines.
1025 analogtv_setup_levels(analogtv *it, double avgheight)
1028 static const double levelfac[3]={-7.5, 5.5, 24.5};
1030 for (height=0; height<avgheight+2.0 && height<=ANALOGTV_MAX_LINEHEIGHT; height++) {
1032 for (i=0; i<height; i++) {
1033 it->leveltable[height][i].index = 2;
1037 it->leveltable[height][0].index=0;
1040 if (height >= 1) it->leveltable[height][height-1].index=0;
1043 it->leveltable[height][1].index=1;
1044 if (height >= 2) it->leveltable[height][height-2].index=1;
1047 for (i=0; i<height; i++) {
1048 it->leveltable[height][i].value =
1049 (40.0 + levelfac[it->leveltable[height][i].index]*puramp(it, 3.0, 6.0, 1.0)) / 256.0;
1056 analogtv_blast_imagerow(analogtv *it,
1057 float *rgbf, float *rgbf_end,
1062 char *level_copyfrom[3];
1063 int xrepl=it->xrepl;
1064 for (i=0; i<3; i++) level_copyfrom[i]=NULL;
1066 for (y=ytop; y<ybot; y++) {
1067 int level=it->leveltable[ybot-ytop][y-ytop].index;
1068 double levelmult=it->leveltable[ybot-ytop][y-ytop].value;
1071 rowdata=it->image->data + y*it->image->bytes_per_line;
1073 /* Fast special cases to avoid the slow XPutPixel. Ugh. It goes to show
1074 why standard graphics sw has to be fast, or else people will have to
1075 work around it and risk incompatibility. The quickdraw folks
1076 understood this. The other answer would be for X11 to have fewer
1077 formats for bitm.. oh, never mind. If neither of these cases work
1078 (they probably cover 99% of setups) it falls back on the Xlib
1081 if (level_copyfrom[level]) {
1082 memcpy(rowdata, level_copyfrom[level], it->image->bytes_per_line);
1085 level_copyfrom[level] = rowdata;
1089 else if (it->image->format==ZPixmap &&
1090 it->image->bits_per_pixel==32 &&
1091 sizeof(unsigned int)==4 &&
1092 it->image->byte_order==localbyteorder) {
1093 /* int is more likely to be 32 bits than long */
1094 unsigned int *pixelptr=(unsigned int *)rowdata;
1097 for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) {
1098 int ntscri=rpf[0]*levelmult;
1099 int ntscgi=rpf[1]*levelmult;
1100 int ntscbi=rpf[2]*levelmult;
1101 if (ntscri>=ANALOGTV_CV_MAX) ntscri=ANALOGTV_CV_MAX-1;
1102 if (ntscgi>=ANALOGTV_CV_MAX) ntscgi=ANALOGTV_CV_MAX-1;
1103 if (ntscbi>=ANALOGTV_CV_MAX) ntscbi=ANALOGTV_CV_MAX-1;
1104 pix = (it->red_values[ntscri] |
1105 it->green_values[ntscgi] |
1106 it->blue_values[ntscbi]);
1110 if (xrepl>=3) pixelptr[2] = pix;
1115 else if (it->image->format==ZPixmap &&
1116 it->image->bits_per_pixel==16 &&
1117 sizeof(unsigned short)==2 &&
1118 float_extraction_works &&
1119 it->image->byte_order==localbyteorder) {
1120 unsigned short *pixelptr=(unsigned short *)rowdata;
1122 float_extract_t r1,g1,b1;
1125 for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) {
1126 r2=rpf[0]; g2=rpf[1]; b2=rpf[2];
1127 r1.f=r2 * levelmult+float_low8_ofs;
1128 g1.f=g2 * levelmult+float_low8_ofs;
1129 b1.f=b2 * levelmult+float_low8_ofs;
1130 pix = (it->red_values[r1.i & 0x3ff] |
1131 it->green_values[g1.i & 0x3ff] |
1132 it->blue_values[b1.i & 0x3ff]);
1136 if (xrepl>=3) pixelptr[2] = pix;
1141 else if (it->image->format==ZPixmap &&
1142 it->image->bits_per_pixel==16 &&
1143 sizeof(unsigned short)==2 &&
1144 it->image->byte_order==localbyteorder) {
1145 unsigned short *pixelptr=(unsigned short *)rowdata;
1148 for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) {
1149 int r1=rpf[0] * levelmult;
1150 int g1=rpf[1] * levelmult;
1151 int b1=rpf[2] * levelmult;
1152 if (r1>=ANALOGTV_CV_MAX) r1=ANALOGTV_CV_MAX-1;
1153 if (g1>=ANALOGTV_CV_MAX) g1=ANALOGTV_CV_MAX-1;
1154 if (b1>=ANALOGTV_CV_MAX) b1=ANALOGTV_CV_MAX-1;
1155 pix = it->red_values[r1] | it->green_values[g1] | it->blue_values[b1];
1159 if (xrepl>=3) pixelptr[2] = pix;
1165 for (x=0, rpf=rgbf; rpf!=rgbf_end ; x++, rpf+=3) {
1166 int ntscri=rpf[0]*levelmult;
1167 int ntscgi=rpf[1]*levelmult;
1168 int ntscbi=rpf[2]*levelmult;
1169 if (ntscri>=ANALOGTV_CV_MAX) ntscri=ANALOGTV_CV_MAX-1;
1170 if (ntscgi>=ANALOGTV_CV_MAX) ntscgi=ANALOGTV_CV_MAX-1;
1171 if (ntscbi>=ANALOGTV_CV_MAX) ntscbi=ANALOGTV_CV_MAX-1;
1172 for (j=0; j<xrepl; j++) {
1173 XPutPixel(it->image, x*xrepl + j, y,
1174 it->red_values[ntscri] | it->green_values[ntscgi] |
1175 it->blue_values[ntscbi]);
1184 analogtv_draw(analogtv *it)
1187 int scanstart_i,scanend_i,squishright_i,squishdiv,pixrate;
1188 float *rgb_start, *rgb_end;
1191 int /*bigloadchange,*/drawcount;
1194 int overall_top, overall_bot;
1196 float *raw_rgb_start=(float *)calloc(it->subwidth*3, sizeof(float));
1197 float *raw_rgb_end=raw_rgb_start+3*it->subwidth;
1200 if (! raw_rgb_start) return;
1201 analogtv_setup_frame(it);
1202 analogtv_set_demod(it);
1204 /* rx_signal has an extra 2 lines at the end, where we copy the
1205 first 2 lines so we can index into it while only worrying about
1206 wraparound on a per-line level */
1207 memcpy(&it->rx_signal[ANALOGTV_SIGNAL_LEN],
1209 2*ANALOGTV_H*sizeof(it->rx_signal[0]));
1214 /* if (it->hashnoise_on) baseload=0.5; */
1216 /*bigloadchange=1;*/
1218 it->crtload[ANALOGTV_TOP-1]=baseload;
1219 puheight = puramp(it, 2.0, 1.0, 1.3) * it->height_control *
1220 (1.125 - 0.125*puramp(it, 2.0, 2.0, 1.1));
1222 analogtv_setup_levels(it, puheight * (double)it->useheight/(double)ANALOGTV_VISLINES);
1224 overall_top=it->useheight;
1227 for (lineno=ANALOGTV_TOP; lineno<ANALOGTV_BOT; lineno++) {
1228 int slineno=lineno-ANALOGTV_TOP;
1229 int ytop=(int)((slineno*it->useheight/ANALOGTV_VISLINES -
1230 it->useheight/2)*puheight) + it->useheight/2;
1231 int ybot=(int)(((slineno+1)*it->useheight/ANALOGTV_VISLINES -
1232 it->useheight/2)*puheight) + it->useheight/2;
1234 int linesig=analogtv_line_signature(input,lineno)
1235 + it->hashnoise_times[lineno];
1237 double *signal=(it->rx_signal + ((lineno + it->cur_vsync +
1238 ANALOGTV_V)%ANALOGTV_V) * ANALOGTV_H +
1239 it->line_hsync[lineno]);
1241 if (ytop==ybot) continue;
1242 if (ybot<0 || ytop>it->useheight) continue;
1244 if (ybot>it->useheight) ybot=it->useheight;
1246 if (ybot > ytop+ANALOGTV_MAX_LINEHEIGHT) ybot=ytop+ANALOGTV_MAX_LINEHEIGHT;
1248 if (ytop < overall_top) overall_top=ytop;
1249 if (ybot > overall_bot) overall_bot=ybot;
1251 if (lineno==it->shrinkpulse) {
1253 /*bigloadchange=1;*/
1258 if (it->hashnoise_rpm>0.0 &&
1261 (slineno<20 && it->flutter_horiz_desync) ||
1262 it->gaussiannoise_level>30 ||
1263 ((it->gaussiannoise_level>2.0 ||
1264 it->multipath) && random()%4) ||
1265 linesig != it->onscreen_signature[lineno])) {
1268 it->onscreen_signature[lineno] = linesig;
1273 Interpolate the 600-dotclock line into however many horizontal
1274 screen pixels we're using, and convert to RGB.
1276 We add some 'bloom', variations in the horizontal scan width with
1277 the amount of brightness, extremely common on period TV sets. They
1278 had a single oscillator which generated both the horizontal scan and
1279 (during the horizontal retrace interval) the high voltage for the
1280 electron beam. More brightness meant more load on the oscillator,
1281 which caused an decrease in horizontal deflection. Look for
1284 Also, the A2 did a bad job of generating horizontal sync pulses
1285 during the vertical blanking interval. This, and the fact that the
1286 horizontal frequency was a bit off meant that TVs usually went a bit
1287 out of sync during the vertical retrace, and the top of the screen
1288 would be bent a bit to the left or right. Look for (shiftthisrow).
1290 We also add a teeny bit of left overscan, just enough to be
1291 annoying, but you can still read the left column of text.
1293 We also simulate compression & brightening on the right side of the
1294 screen. Most TVs do this, but you don't notice because they overscan
1295 so it's off the right edge of the CRT. But the A2 video system used
1296 so much of the horizontal scan line that you had to crank the
1297 horizontal width down in order to not lose the right few characters,
1298 and you'd see the compression on the right edge. Associated with
1299 compression is brightening; since the electron beam was scanning
1300 slower, the same drive signal hit the phosphor harder. Look for
1301 (squishright_i) and (squishdiv).
1306 double ncl/*,diff*/;
1308 for (i=0; i<ANALOGTV_PIC_LEN; i++) {
1309 totsignal += signal[i];
1311 totsignal *= it->agclevel;
1312 ncl = 0.95 * it->crtload[lineno-1] +
1314 (totsignal-30000)/100000.0 +
1315 (slineno>184 ? (slineno-184)*(lineno-184)*0.001 * it->squeezebottom
1317 /*diff=ncl - it->crtload[lineno];*/
1318 /*bigloadchange = (diff>0.01 || diff<-0.01);*/
1319 it->crtload[lineno]=ncl;
1323 double bloomthisrow,shiftthisrow;
1324 double viswidth,middle;
1328 bloomthisrow = -10.0 * it->crtload[lineno];
1329 if (bloomthisrow<-10.0) bloomthisrow=-10.0;
1330 if (bloomthisrow>2.0) bloomthisrow=2.0;
1332 shiftthisrow=it->horiz_desync * (exp(-0.17*slineno) *
1333 (0.7+cos(slineno*0.6)));
1338 viswidth=ANALOGTV_PIC_LEN * 0.79 - 5.0*bloomthisrow;
1339 middle=ANALOGTV_PIC_LEN/2 - shiftthisrow;
1341 scanwidth=it->width_control * puramp(it, 0.5, 0.3, 1.0);
1343 scw=it->subwidth*scanwidth;
1344 if (scw>it->subwidth) scw=it->usewidth;
1345 scl=it->subwidth/2 - scw/2;
1346 scr=it->subwidth/2 + scw/2;
1348 pixrate=(int)((viswidth*65536.0*1.0)/it->subwidth)/scanwidth;
1349 scanstart_i=(int)((middle-viswidth*0.5)*65536.0);
1350 scanend_i=(ANALOGTV_PIC_LEN-1)*65536;
1351 squishright_i=(int)((middle+viswidth*(0.25 + 0.25*puramp(it, 2.0, 0.0, 1.1)
1352 - it->squish_control)) *65536.0);
1353 squishdiv=it->subwidth/15;
1355 rgb_start=raw_rgb_start+scl*3;
1356 rgb_end=raw_rgb_start+scr*3;
1358 assert(scanstart_i>=0);
1361 if (0) printf("scan %d: %0.3f %0.3f %0.3f scl=%d scr=%d scw=%d\n",
1363 scanstart_i/65536.0,
1364 squishright_i/65536.0,
1371 for (y=ytop; y<ybot; y++) {
1372 int level=analogtv_level(it, y, ytop, ybot);
1373 double levelmult=analogtv_levelmult(it, level);
1374 double levelmult_y = levelmult * it->contrast_control
1375 * puramp(it, 1.0, 0.0, 1.0) / (0.5+0.5*puheight) * 0.070;
1376 double levelmult_iq = levelmult * 0.090;
1378 struct analogtv_yiq_s *yiq=it->yiq;
1379 analogtv_ntsc_to_yiq(it, lineno, signal,
1380 (scanstart_i>>16)-10, (scanend_i>>16)+10);
1385 while (i<0 && x<it->usewidth) {
1386 XPutPixel(it->image, x, y, it->colors[0]);
1391 while (i<scanend_i && x<it->usewidth) {
1392 double pixfrac=(i&0xffff)/65536.0;
1393 double invpixfrac=(1.0-pixfrac);
1395 int yli,ili,qli,cmi;
1397 double interpy=(yiq[pati].y*invpixfrac
1398 + yiq[pati+1].y*pixfrac) * levelmult_y;
1399 double interpi=(yiq[pati].i*invpixfrac
1400 + yiq[pati+1].i*pixfrac) * levelmult_iq;
1401 double interpq=(yiq[pati].q*invpixfrac
1402 + yiq[pati+1].q*pixfrac) * levelmult_iq;
1404 yli = (int)(interpy * it->cmap_y_levels);
1405 ili = (int)((interpi+0.5) * it->cmap_i_levels);
1406 qli = (int)((interpq+0.5) * it->cmap_q_levels);
1408 if (yli>=it->cmap_y_levels) yli=it->cmap_y_levels-1;
1410 if (ili>=it->cmap_i_levels) ili=it->cmap_i_levels-1;
1412 if (qli>=it->cmap_q_levels) qli=it->cmap_q_levels-1;
1414 cmi=qli + it->cmap_i_levels*(ili + it->cmap_q_levels*yli);
1417 if ((random()%65536)==0) {
1418 printf("%0.3f %0.3f %0.3f => %d %d %d => %d\n",
1419 interpy, interpi, interpq,
1425 for (j=0; j<it->xrepl; j++) {
1426 XPutPixel(it->image, x, y,
1430 if (i >= squishright_i) {
1431 pixmultinc += pixmultinc/squishdiv;
1435 while (x<it->usewidth) {
1436 XPutPixel(it->image, x, y, it->colors[0]);
1442 struct analogtv_yiq_s *yiq=it->yiq;
1443 analogtv_ntsc_to_yiq(it, lineno, signal,
1444 (scanstart_i>>16)-10, (scanend_i>>16)+10);
1446 pixbright=it->contrast_control * puramp(it, 1.0, 0.0, 1.0)
1447 / (0.5+0.5*puheight) * 1024.0/100.0;
1449 i=scanstart_i; rrp=rgb_start;
1450 while (i<0 && rrp!=rgb_end) {
1451 rrp[0]=rrp[1]=rrp[2]=0;
1455 while (i<scanend_i && rrp!=rgb_end) {
1456 double pixfrac=(i&0xffff)/65536.0;
1457 double invpixfrac=1.0-pixfrac;
1461 double interpy=(yiq[pati].y*invpixfrac + yiq[pati+1].y*pixfrac);
1462 double interpi=(yiq[pati].i*invpixfrac + yiq[pati+1].i*pixfrac);
1463 double interpq=(yiq[pati].q*invpixfrac + yiq[pati+1].q*pixfrac);
1466 According to the NTSC spec, Y,I,Q are generated as:
1468 y=0.30 r + 0.59 g + 0.11 b
1469 i=0.60 r - 0.28 g - 0.32 b
1470 q=0.21 r - 0.52 g + 0.31 b
1472 So if you invert the implied 3x3 matrix you get what standard
1473 televisions implement with a bunch of resistors (or directly in the
1476 r = y + 0.948 i + 0.624 q
1477 g = y - 0.276 i - 0.639 q
1478 b = y - 1.105 i + 1.729 q
1481 r=(interpy + 0.948*interpi + 0.624*interpq) * pixbright;
1482 g=(interpy - 0.276*interpi - 0.639*interpq) * pixbright;
1483 b=(interpy - 1.105*interpi + 1.729*interpq) * pixbright;
1491 if (i>=squishright_i) {
1492 pixmultinc += pixmultinc/squishdiv;
1493 pixbright += pixbright/squishdiv/2;
1498 while (rrp != rgb_end) {
1499 rrp[0]=rrp[1]=rrp[2]=0.0;
1503 analogtv_blast_imagerow(it, raw_rgb_start, raw_rgb_end,
1507 free(raw_rgb_start);
1510 /* poor attempt at visible retrace */
1511 for (i=0; i<15; i++) {
1512 int ytop=(int)((i*it->useheight/15 -
1513 it->useheight/2)*puheight) + it->useheight/2;
1514 int ybot=(int)(((i+1)*it->useheight/15 -
1515 it->useheight/2)*puheight) + it->useheight/2;
1516 int div=it->usewidth*3/2;
1518 for (x=0; x<it->usewidth; x++) {
1519 y = ytop + (ybot-ytop)*x / div;
1520 if (y<0 || y>=it->useheight) continue;
1521 XPutPixel(it->image, x, y, 0xffffff);
1526 if (it->need_clear) {
1527 XClearWindow(it->dpy, it->window);
1531 if (overall_top>0) {
1532 XClearArea(it->dpy, it->window,
1533 it->screen_xo, it->screen_yo,
1534 it->usewidth, overall_top, 0);
1536 if (it->useheight > overall_bot) {
1537 XClearArea(it->dpy, it->window,
1538 it->screen_xo, it->screen_yo+overall_bot,
1539 it->usewidth, it->useheight-overall_bot, 0);
1542 if (overall_bot > overall_top) {
1544 #ifdef HAVE_XSHM_EXTENSION
1545 XShmPutImage(it->dpy, it->window, it->gc, it->image,
1547 it->screen_xo, it->screen_yo+overall_top,
1548 it->usewidth, overall_bot - overall_top,
1552 XPutImage(it->dpy, it->window, it->gc, it->image,
1554 it->screen_xo, it->screen_yo+overall_top,
1555 it->usewidth, overall_bot - overall_top);
1564 gettimeofday(&tv,NULL);
1566 fps=1.0/((tv.tv_sec - it->last_display_time.tv_sec)
1567 + 0.000001*(tv.tv_usec - it->last_display_time.tv_usec));
1568 sprintf(buf, "FPS=%0.1f",fps);
1569 XDrawString(it->dpy, it->window, it->gc, 50, it->useheight*2/3,
1572 it->last_display_time=tv;
1578 analogtv_input_allocate()
1580 analogtv_input *ret=(analogtv_input *)calloc(1,sizeof(analogtv_input));
1586 This takes a screen image and encodes it as a video camera would,
1587 including all the bandlimiting and YIQ modulation.
1588 This isn't especially tuned for speed.
1592 analogtv_load_ximage(analogtv *it, analogtv_input *input, XImage *pic_im)
1599 XColor col1[ANALOGTV_PIC_LEN];
1600 XColor col2[ANALOGTV_PIC_LEN];
1601 int multiq[ANALOGTV_PIC_LEN+4];
1602 int y_overscan=5; /* overscan this much top and bottom */
1603 int y_scanlength=ANALOGTV_VISLINES+2*y_overscan;
1605 img_w=pic_im->width;
1606 img_h=pic_im->height;
1608 for (i=0; i<ANALOGTV_PIC_LEN+4; i++) {
1609 double phase=90.0-90.0*i;
1611 multiq[i]=(int)(-cos(3.1415926/180.0*(phase-303)) * 4096.0 * ampl);
1614 for (y=0; y<y_scanlength; y++) {
1615 int picy1=(y*img_h)/y_scanlength;
1616 int picy2=(y*img_h + y_scanlength/2)/y_scanlength;
1618 for (x=0; x<ANALOGTV_PIC_LEN; x++) {
1619 int picx=(x*img_w)/ANALOGTV_PIC_LEN;
1620 col1[x].pixel=XGetPixel(pic_im, picx, picy1);
1621 col2[x].pixel=XGetPixel(pic_im, picx, picy2);
1623 XQueryColors(it->dpy, it->colormap, col1, ANALOGTV_PIC_LEN);
1624 XQueryColors(it->dpy, it->colormap, col2, ANALOGTV_PIC_LEN);
1626 for (i=0; i<7; i++) fyx[i]=fyy[i]=0;
1627 for (i=0; i<4; i++) fix[i]=fiy[i]=fqx[i]=fqy[i]=0.0;
1629 for (x=0; x<ANALOGTV_PIC_LEN; x++) {
1631 int filty,filti,filtq;
1634 y=0.30 r + 0.59 g + 0.11 b
1635 i=0.60 r - 0.28 g - 0.32 b
1636 q=0.21 r - 0.52 g + 0.31 b
1637 The coefficients below are in .4 format */
1639 rawy=( 5*col1[x].red + 11*col1[x].green + 2*col1[x].blue +
1640 5*col2[x].red + 11*col2[x].green + 2*col2[x].blue)>>7;
1641 rawi=(10*col1[x].red - 4*col1[x].green - 5*col1[x].blue +
1642 10*col2[x].red - 4*col2[x].green - 5*col2[x].blue)>>7;
1643 rawq=( 3*col1[x].red - 8*col1[x].green + 5*col1[x].blue +
1644 3*col2[x].red - 8*col2[x].green + 5*col2[x].blue)>>7;
1646 /* Filter y at with a 4-pole low-pass Butterworth filter at 3.5 MHz
1647 with an extra zero at 3.5 MHz, from
1648 mkfilter -Bu -Lp -o 4 -a 2.1428571429e-01 0 -Z 2.5e-01 -l */
1650 fyx[0] = fyx[1]; fyx[1] = fyx[2]; fyx[2] = fyx[3];
1651 fyx[3] = fyx[4]; fyx[4] = fyx[5]; fyx[5] = fyx[6];
1652 fyx[6] = (rawy * 1897) >> 16;
1653 fyy[0] = fyy[1]; fyy[1] = fyy[2]; fyy[2] = fyy[3];
1654 fyy[3] = fyy[4]; fyy[4] = fyy[5]; fyy[5] = fyy[6];
1655 fyy[6] = (fyx[0]+fyx[6]) + 4*(fyx[1]+fyx[5]) + 7*(fyx[2]+fyx[4]) + 8*fyx[3]
1656 + ((-151*fyy[2] + 8115*fyy[3] - 38312*fyy[4] + 36586*fyy[5]) >> 16);
1659 /* Filter I at 1.5 MHz. 3 pole Butterworth from
1660 mkfilter -Bu -Lp -o 3 -a 1.0714285714e-01 0 */
1662 fix[0] = fix[1]; fix[1] = fix[2]; fix[2] = fix[3];
1663 fix[3] = (rawi * 1413) >> 16;
1664 fiy[0] = fiy[1]; fiy[1] = fiy[2]; fiy[2] = fiy[3];
1665 fiy[3] = (fix[0]+fix[3]) + 3*(fix[1]+fix[2])
1666 + ((16559*fiy[0] - 72008*fiy[1] + 109682*fiy[2]) >> 16);
1669 /* Filter Q at 0.5 MHz. 3 pole Butterworth from
1670 mkfilter -Bu -Lp -o 3 -a 3.5714285714e-02 0 -l */
1672 fqx[0] = fqx[1]; fqx[1] = fqx[2]; fqx[2] = fqx[3];
1673 fqx[3] = (rawq * 75) >> 16;
1674 fqy[0] = fqy[1]; fqy[1] = fqy[2]; fqy[2] = fqy[3];
1675 fqy[3] = (fqx[0]+fqx[3]) + 3 * (fqx[1]+fqx[2])
1676 + ((2612*fqy[0] - 9007*fqy[1] + 10453 * fqy[2]) >> 12);
1680 composite = filty + ((multiq[x] * filti + multiq[x+3] * filtq)>>12);
1681 composite = ((composite*100)>>14) + ANALOGTV_BLACK_LEVEL;
1682 if (composite>125) composite=125;
1683 if (composite<0) composite=0;
1684 input->signal[y-y_overscan+ANALOGTV_TOP][x+ANALOGTV_PIC_START] = composite;
1692 void analogtv_channel_noise(analogtv_input *it, analogtv_input *s2)
1695 int change=random()%ANALOGTV_V;
1696 unsigned int fastrnd=random();
1697 double hso=(int)(random()%1000)-500;
1698 int yofs=random()%ANALOGTV_V;
1701 for (y=change; y<ANALOGTV_V; y++) {
1702 int s2y=(y+yofs)%ANALOGTV_V;
1704 int noiselevel=60000 / (y-change+100);
1706 it->line_hsync[y] = s2->line_hsync[y] + (int)hso;
1708 for (x=0; x<ANALOGTV_H; x++) {
1710 filt+= (-filt/16) + (int)(fastrnd&0xfff)-0x800;
1711 noise=(filt*noiselevel)>>16;
1712 newsig=s2->signal[s2y][x] + noise;
1713 if (newsig>120) newsig=120;
1714 if (newsig<0) newsig=0;
1715 it->signal[y][x]=newsig;
1723 void analogtv_add_signal(analogtv *it, analogtv_reception *rec)
1725 analogtv_input *inp=rec->input;
1726 double *ps=it->rx_signal;
1727 double *pe=it->rx_signal + ANALOGTV_SIGNAL_LEN;
1729 signed char *ss=&inp->signal[0][0];
1730 signed char *se=&inp->signal[0][0] + ANALOGTV_SIGNAL_LEN;
1731 signed char *s=ss + ((unsigned)rec->ofs % ANALOGTV_SIGNAL_LEN);
1733 int ec=it->channel_change_cycles;
1734 double level=rec->level;
1735 double hfloss=rec->hfloss;
1736 unsigned int fastrnd=random();
1739 /* assert((se-ss)%4==0 && (se-s)%4==0); */
1741 /* duplicate the first line into the Nth line to ease wraparound computation */
1742 memcpy(inp->signal[ANALOGTV_V], inp->signal[0],
1743 ANALOGTV_H * sizeof(inp->signal[0][0]));
1745 for (i=0; i<8; i++) dp[i]=0.0;
1750 /* Do a big noisy transition. We can make the transition noise of
1751 high constant strength regardless of signal strength.
1753 There are two separate state machines. here, One is the noise
1754 process and the other is the
1756 We don't bother with the FIR filter here
1761 while (p!=pe && ec>0) {
1763 double sig0=(double)s[0];
1764 double noise = ((int)fastrnd-(int)0x7fffffff) * (50.0/(double)0x7fffffff);
1765 fastrnd = (fastrnd*1103515245+12345) & 0xffffffffu;
1767 p[0] += sig0 * level * (1.0 - noise_ampl) + noise * noise_ampl;
1769 noise_ampl *= 0.99995;
1780 double sig0,sig1,sig2,sig3,sigr;
1787 dp[0]=sig0+sig1+sig2+sig3;
1789 /* Get the video out signal, and add some ghosting, typical of RF
1790 monitor cables. This corresponds to a pretty long cable, but
1794 sigr=(dp[1]*rec->ghostfir[0] + dp[2]*rec->ghostfir[1] +
1795 dp[3]*rec->ghostfir[2] + dp[4]*rec->ghostfir[3]);
1796 dp[4]=dp[3]; dp[3]=dp[2]; dp[2]=dp[1]; dp[1]=dp[0];
1798 p[0] += (sig0+sigr + sig2*hfloss) * level;
1799 p[1] += (sig1+sigr + sig3*hfloss) * level;
1800 p[2] += (sig2+sigr + sig0*hfloss) * level;
1801 p[3] += (sig3+sigr + sig1*hfloss) * level;
1805 if (s>=se) s = ss + (s-se);
1808 it->rx_signal_level =
1809 sqrt(it->rx_signal_level * it->rx_signal_level +
1810 (level * level * (1.0 + 4.0*(rec->ghostfir[0] + rec->ghostfir[1] +
1811 rec->ghostfir[2] + rec->ghostfir[3]))));
1814 it->channel_change_cycles=0;
1820 if (it->hashnoise_times[lineno]) {
1821 int hnt=it->hashnoise_times[lineno] - input->line_hsync[lineno];
1823 if (hnt>=0 && hnt<ANALOGTV_PIC_LEN) {
1825 double cur=frand(150.0)-20.0;
1826 int len=random()%15+3;
1827 if (len > ANALOGTV_PIC_LEN-hnt) len=ANALOGTV_PIC_LEN-hnt;
1828 for (i=0; i<len; i++) {
1829 double sig=signal[hnt];
1832 cur += frand(5.0)-5.0;
1833 maxampl = maxampl*0.9;
1843 void analogtv_init_signal(analogtv *it, double noiselevel)
1845 double *ps=it->rx_signal;
1846 double *pe=it->rx_signal + ANALOGTV_SIGNAL_LEN;
1848 unsigned int fastrnd=random();
1849 double nm1=0.0,nm2=0.0;
1850 double noisemul = sqrt(noiselevel*150)/(double)0x7fffffff;
1854 nm1 = ((int)fastrnd-(int)0x7fffffff) * noisemul;
1856 fastrnd = (fastrnd*1103515245+12345) & 0xffffffffu;
1859 it->rx_signal_level = noiselevel;
1863 analogtv_reception_update(analogtv_reception *rec)
1867 if (rec->multipath > 0.0) {
1868 for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
1869 rec->ghostfir2[i] +=
1870 -(rec->ghostfir2[i]/16.0) + rec->multipath * (frand(0.02)-0.01);
1872 if (random()%20==0) {
1873 rec->ghostfir2[random()%(ANALOGTV_GHOSTFIR_LEN)]
1874 = rec->multipath * (frand(0.08)-0.04);
1876 for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
1877 rec->ghostfir[i] = 0.8*rec->ghostfir[i] + 0.2*rec->ghostfir2[i];
1881 rec->hfloss2 += -(rec->hfloss2/16.0) + rec->multipath * (frand(0.08)-0.04);
1882 rec->hfloss = 0.5*rec->hfloss + 0.5*rec->hfloss2;
1886 for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
1887 rec->ghostfir[i] = (i>=ANALOGTV_GHOSTFIR_LEN/2) ? ((i&1) ? +0.04 : -0.08) /ANALOGTV_GHOSTFIR_LEN
1894 /* jwz: since MacOS doesn't have "6x10", I dumped this font to an XBM...
1897 #include "images/6x10font.xbm"
1900 analogtv_make_font(Display *dpy, Window window, analogtv_font *f,
1901 int w, int h, char *fontname)
1908 XWindowAttributes xgwa;
1913 XGetWindowAttributes (dpy, window, &xgwa);
1915 if (fontname && !strcmp (fontname, "6x10")) {
1917 text_pm = XCreatePixmapFromBitmapData (dpy, window,
1918 (char *) font6x10_bits,
1922 f->text_im = XGetImage(dpy, text_pm, 0, 0, font6x10_width, font6x10_height,
1924 XFreePixmap(dpy, text_pm);
1926 } else if (fontname) {
1928 font = XLoadQueryFont (dpy, fontname);
1930 fprintf(stderr, "analogtv: can't load font %s\n", fontname);
1934 text_pm=XCreatePixmap(dpy, window, 256*f->char_w, f->char_h, 1);
1936 memset(&gcv, 0, sizeof(gcv));
1940 gc=XCreateGC(dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
1942 XSetForeground(dpy, gc, 0);
1943 XFillRectangle(dpy, text_pm, gc, 0, 0, 256*f->char_w, f->char_h);
1944 XSetForeground(dpy, gc, 1);
1945 for (i=0; i<256; i++) {
1947 int x=f->char_w*i+1;
1948 int y=f->char_h*8/10;
1949 XDrawString(dpy, text_pm, gc, x, y, &c, 1);
1951 f->text_im = XGetImage(dpy, text_pm, 0, 0, 256*f->char_w, f->char_h,
1954 XWriteBitmapFile(dpy, "/tmp/tvfont.xbm", text_pm,
1955 256*f->char_w, f->char_h, -1, -1);
1958 XFreePixmap(dpy, text_pm);
1960 f->text_im = XCreateImage(dpy, xgwa.visual, 1, XYPixmap, 0, 0,
1961 256*f->char_w, f->char_h, 8, 0);
1962 f->text_im->data = (char *)calloc(f->text_im->height,
1963 f->text_im->bytes_per_line);
1971 analogtv_font_pixel(analogtv_font *f, int c, int x, int y)
1973 if (x<0 || x>=f->char_w) return 0;
1974 if (y<0 || y>=f->char_h) return 0;
1975 if (c<0 || c>=256) return 0;
1976 return XGetPixel(f->text_im, c*f->char_w + x, y) ? 1 : 0;
1980 analogtv_font_set_pixel(analogtv_font *f, int c, int x, int y, int value)
1982 if (x<0 || x>=f->char_w) return;
1983 if (y<0 || y>=f->char_h) return;
1984 if (c<0 || c>=256) return;
1986 XPutPixel(f->text_im, c*f->char_w + x, y, value);
1990 analogtv_font_set_char(analogtv_font *f, int c, char *s)
1994 if (c<0 || c>=256) return;
1996 for (y=0; y<f->char_h; y++) {
1997 for (x=0; x<f->char_w; x++) {
1999 value=(*s==' ') ? 0 : 1;
2000 analogtv_font_set_pixel(f, c, x, y, value);
2007 analogtv_lcp_to_ntsc(double luma, double chroma, double phase, int ntsc[4])
2010 for (i=0; i<4; i++) {
2011 double w=90.0*i + phase;
2012 double val=luma + chroma * (cos(3.1415926/180.0*w));
2013 if (val<0.0) val=0.0;
2014 if (val>127.0) val=127.0;
2020 analogtv_draw_solid(analogtv_input *input,
2021 int left, int right, int top, int bot,
2026 if (right-left<4) right=left+4;
2027 if (bot-top<1) bot=top+1;
2029 for (y=top; y<bot; y++) {
2030 for (x=left; x<right; x++) {
2031 input->signal[y][x] = ntsc[x&3];
2038 analogtv_draw_solid_rel_lcp(analogtv_input *input,
2039 double left, double right, double top, double bot,
2040 double luma, double chroma, double phase)
2044 int topi=(int)(ANALOGTV_TOP + ANALOGTV_VISLINES*top);
2045 int boti=(int)(ANALOGTV_TOP + ANALOGTV_VISLINES*bot);
2046 int lefti=(int)(ANALOGTV_VIS_START + ANALOGTV_VIS_LEN*left);
2047 int righti=(int)(ANALOGTV_VIS_START + ANALOGTV_VIS_LEN*right);
2049 analogtv_lcp_to_ntsc(luma, chroma, phase, ntsc);
2050 analogtv_draw_solid(input, lefti, righti, topi, boti, ntsc);
2055 analogtv_draw_char(analogtv_input *input, analogtv_font *f,
2056 int c, int x, int y, int ntsc[4])
2058 int yc,xc,ys,xs,pix;
2060 for (yc=0; yc<f->char_h; yc++) {
2061 for (ys=y + yc*f->y_mult; ys<y + (yc+1)*f->y_mult; ys++) {
2062 if (ys<0 || ys>=ANALOGTV_V) continue;
2064 for (xc=0; xc<f->char_w; xc++) {
2065 pix=analogtv_font_pixel(f, c, xc, yc);
2067 for (xs=x + xc*f->x_mult; xs<x + (xc+1)*f->x_mult; xs++) {
2068 if (xs<0 || xs>=ANALOGTV_H) continue;
2070 input->signal[ys][xs] = ntsc[xs&3];
2079 analogtv_draw_string(analogtv_input *input, analogtv_font *f,
2080 char *s, int x, int y, int ntsc[4])
2083 analogtv_draw_char(input, f, *s, x, y, ntsc);
2090 analogtv_draw_string_centered(analogtv_input *input, analogtv_font *f,
2091 char *s, int x, int y, int ntsc[4])
2093 int width=strlen(s) * f->char_w * 4;
2096 analogtv_draw_string(input, f, s, x, y, ntsc);
2100 static const char hextonib[128] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2101 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2102 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2103 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
2104 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
2105 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2106 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
2107 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
2110 Much of this function was adapted from logo.c
2113 analogtv_draw_xpm(analogtv *tv, analogtv_input *input,
2114 const char * const *xpm, int left, int top)
2119 int ncolors, nbytes;
2122 int r; int g; int b;
2126 if (4 != sscanf ((const char *) *xpm,
2128 &xpmw, &xpmh, &ncolors, &nbytes, &dummyc))
2130 if (ncolors < 1 || ncolors > 255)
2132 if (nbytes != 1) /* a serious limitation */
2136 for (i = 0; i < ncolors; i++) {
2137 const char *line = *xpm;
2138 int colori = ((unsigned char)*line++)&0xff;
2143 while (*line == ' ' || *line == '\t')
2146 if (which != 'c' && which != 'm')
2148 while (*line == ' ' || *line == '\t')
2150 if (!strncasecmp(line, "None", 4))
2159 r = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
2161 g = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
2163 b = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
2178 for (y=0; y<xpmh; y++) {
2179 const char *line = *xpm++;
2181 if (tvy<ANALOGTV_TOP || tvy>=ANALOGTV_BOT) continue;
2183 for (x=0; x<xpmw; x++) {
2184 int cbyte=((unsigned char)line[x])&0xff;
2187 if (tvx<ANALOGTV_PIC_START || tvx+4>ANALOGTV_PIC_END) continue;
2189 rawy=( 5*cmap[cbyte].r + 11*cmap[cbyte].g + 2*cmap[cbyte].b) / 64;
2190 rawi=(10*cmap[cbyte].r - 4*cmap[cbyte].g - 5*cmap[cbyte].b) / 64;
2191 rawq=( 3*cmap[cbyte].r - 8*cmap[cbyte].g + 5*cmap[cbyte].b) / 64;
2198 for (i=0; i<4; i++) {
2199 if (ntsc[i]>ANALOGTV_WHITE_LEVEL) ntsc[i]=ANALOGTV_WHITE_LEVEL;
2200 if (ntsc[i]<ANALOGTV_BLACK_LEVEL) ntsc[i]=ANALOGTV_BLACK_LEVEL;
2203 input->signal[tvy][tvx+0]= ntsc[(tvx+0)&3];
2204 input->signal[tvy][tvx+1]= ntsc[(tvx+1)&3];
2205 input->signal[tvy][tvx+2]= ntsc[(tvx+2)&3];
2206 input->signal[tvy][tvx+3]= ntsc[(tvx+3)&3];