1 /* analogtv, Copyright (c) 2003 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>
55 #include <X11/Xutil.h>
56 #include <X11/Intrinsic.h>
59 #include "resources.h"
62 #include "grabscreen.h"
67 /* only works on linux + freebsd */
68 #include <machine/cpufunc.h>
70 #define DTIME_DECL u_int64_t dtimes[100]; int n_dtimes
71 #define DTIME_START do {n_dtimes=0; dtimes[n_dtimes++]=rdtsc(); } while (0)
72 #define DTIME dtimes[n_dtimes++]=rdtsc()
73 #define DTIME_SHOW(DIV) \
75 double _dtime_div=(DIV); \
76 printf("time/%.1f: ",_dtime_div); \
77 for (i=1; i<n_dtimes; i++) \
78 printf(" %0.9f",(dtimes[i]-dtimes[i-1])* 1e-9 / _dtime_div); \
85 #define DTIME_START do { } while (0)
86 #define DTIME do { } while (0)
87 #define DTIME_SHOW(DIV) do { } while (0)
92 #define FASTRND (fastrnd = fastrnd*1103515245+12345)
94 static void analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
97 static double puramp(analogtv *it, double tc, double start, double over)
99 double pt=it->powerup-start;
101 if (pt<0.0) return 0.0;
102 if (pt>900.0 || pt/tc>8.0) return 1.0;
104 ret=(1.0-exp(-pt/tc))*over;
105 if (ret>1.0) return 1.0;
110 There are actual standards for TV signals: NTSC and RS-170A describe the
111 system used in the US and Japan. Europe has slightly different systems, but
112 not different enough to make substantially different screensaver displays.
113 Sadly, the standards bodies don't do anything so useful as publish the spec on
114 the web. Best bets are:
116 http://www.ee.washington.edu/conselec/CE/kuhn/ntsc/95x4.htm
117 http://www.ntsc-tv.com/ntsc-index-02.htm
119 In DirectColor or TrueColor modes, it generates pixel values directly from RGB
120 values it calculates across each scan line. In PseudoColor mode, it consider
121 each possible pattern of 5 preceding bit values in each possible position
122 modulo 4 and allocates a color for each. A few things, like the brightening on
123 the right side as the horizontal trace slows down, aren't done in PseudoColor.
125 I'd like to add a bit of visible retrace, but it conflicts with being able to
126 bitcopy the image when fast scrolling. After another couple of CPU
127 generations, we could probably regenerate the whole image from scratch every
128 time. On a P4 2 GHz it can manage this fine for blinking text, but scrolling
132 /* localbyteorder is MSBFirst or LSBFirst */
133 static int localbyteorder;
134 static const double float_low8_ofs=8388608.0;
135 static int float_extraction_works;
147 unsigned int localbyteorder_loc = (MSBFirst<<24) | (LSBFirst<<0);
148 localbyteorder=*(char *)&localbyteorder_loc;
155 float_extraction_works=1;
156 for (i=0; i<256*4; i++) {
157 fe.f=float_low8_ofs+(double)i;
161 printf("Float extraction failed for %d => %d\n",i,ans);
163 float_extraction_works=0;
172 analogtv_set_defaults(analogtv *it, char *prefix)
176 sprintf(buf,"%sTVTint",prefix);
177 it->tint_control = get_float_resource(buf,"TVTint");
178 sprintf(buf,"%sTVColor",prefix);
179 it->color_control = get_float_resource(buf,"TVColor")/100.0;
180 sprintf(buf,"%sTVBrightness",prefix);
181 it->brightness_control = get_float_resource(buf,"TVBrightness") / 100.0;
182 sprintf(buf,"%sTVContrast",prefix);
183 it->contrast_control = get_float_resource(buf,"TVContrast") / 100.0;
184 it->height_control = 1.0;
185 it->width_control = 1.0;
186 it->squish_control = 0.0;
191 it->hashnoise_enable=1;
193 it->horiz_desync=frand(10.0)-5.0;
194 it->squeezebottom=frand(5.0)-1.0;
197 printf("analogtv: prefix=%s\n",prefix);
198 printf(" use: shm=%d cmap=%d color=%d\n",
199 it->use_shm,it->use_cmap,it->use_color);
200 printf(" controls: tint=%g color=%g brightness=%g contrast=%g\n",
201 it->tint_control, it->color_control, it->brightness_control,
202 it->contrast_control);
203 printf(" freq_error %g: %g %d\n",
204 it->freq_error, it->freq_error_inc, it->flutter_tint);
205 printf(" desync: %g %d\n",
206 it->horiz_desync, it->flutter_horiz_desync);
207 printf(" hashnoise rpm: %g\n",
209 printf(" vis: %d %d %d\n",
210 it->visclass, it->visbits, it->visdepth);
211 printf(" shift: %d-%d %d-%d %d-%d\n",
212 it->red_invprec,it->red_shift,
213 it->green_invprec,it->green_shift,
214 it->blue_invprec,it->blue_shift);
215 printf(" size: %d %d %d %d xrepl=%d\n",
216 it->usewidth, it->useheight,
217 it->screen_xo, it->screen_yo, it->xrepl);
219 printf(" ANALOGTV_V=%d\n",ANALOGTV_V);
220 printf(" ANALOGTV_TOP=%d\n",ANALOGTV_TOP);
221 printf(" ANALOGTV_VISLINES=%d\n",ANALOGTV_VISLINES);
222 printf(" ANALOGTV_BOT=%d\n",ANALOGTV_BOT);
223 printf(" ANALOGTV_H=%d\n",ANALOGTV_H);
224 printf(" ANALOGTV_SYNC_START=%d\n",ANALOGTV_SYNC_START);
225 printf(" ANALOGTV_BP_START=%d\n",ANALOGTV_BP_START);
226 printf(" ANALOGTV_CB_START=%d\n",ANALOGTV_CB_START);
227 printf(" ANALOGTV_PIC_START=%d\n",ANALOGTV_PIC_START);
228 printf(" ANALOGTV_PIC_LEN=%d\n",ANALOGTV_PIC_LEN);
229 printf(" ANALOGTV_FP_START=%d\n",ANALOGTV_FP_START);
230 printf(" ANALOGTV_PIC_END=%d\n",ANALOGTV_PIC_END);
231 printf(" ANALOGTV_HASHNOISE_LEN=%d\n",ANALOGTV_HASHNOISE_LEN);
237 extern Bool mono_p; /* shoot me */
240 analogtv_free_image(analogtv *it)
244 #ifdef HAVE_XSHM_EXTENSION
245 destroy_xshm_image(it->dpy, it->image, &it->shm_info);
248 XDestroyImage(it->image);
255 analogtv_alloc_image(analogtv *it)
258 #ifdef HAVE_XSHM_EXTENSION
259 it->image=create_xshm_image(it->dpy, it->xgwa.visual, it->xgwa.depth, ZPixmap, 0,
260 &it->shm_info, it->usewidth, it->useheight);
262 if (!it->image) it->use_shm=0;
265 it->image = XCreateImage(it->dpy, it->xgwa.visual, it->xgwa.depth, ZPixmap, 0, 0,
266 it->usewidth, it->useheight, 8, 0);
267 it->image->data = (char *)calloc(it->image->height, it->image->bytes_per_line);
273 analogtv_configure(analogtv *it)
275 int oldwidth=it->usewidth;
276 int oldheight=it->useheight;
277 int wlim,hlim,height_diff;
279 /* If the window is very small, don't let the image we draw get lower
280 than the actual TV resolution (266x200.)
282 If the aspect ratio of the window is within 20% of a 4:3 ratio,
283 then scale the image to exactly fill the window.
285 Otherwise, center the image either horizontally or vertically,
286 padding on the left+right, or top+bottom, but not both.
288 If it's very close (2.5%) to a multiple of VISLINES, make it exact
289 For example, it maps 1024 => 1000.
291 float percent = 0.20;
292 float min_ratio = 4.0 / 3.0 * (1 - percent);
293 float max_ratio = 4.0 / 3.0 * (1 + percent);
295 float height_snap=0.025;
297 hlim = it->xgwa.height;
298 wlim = it->xgwa.width;
299 ratio = wlim / (float) hlim;
301 if (wlim < 266 || hlim < 200)
307 "size: minimal: %dx%d in %dx%d (%.3f < %.3f < %.3f)\n",
308 wlim, hlim, it->xgwa.width, it->xgwa.height,
309 min_ratio, ratio, max_ratio);
312 else if (ratio > min_ratio && ratio < max_ratio)
316 "size: close enough: %dx%d (%.3f < %.3f < %.3f)\n",
317 wlim, hlim, min_ratio, ratio, max_ratio);
320 else if (ratio > max_ratio)
322 wlim = hlim*max_ratio;
325 "size: center H: %dx%d in %dx%d (%.3f < %.3f < %.3f)\n",
326 wlim, hlim, it->xgwa.width, it->xgwa.height,
327 min_ratio, ratio, max_ratio);
330 else /* ratio < min_ratio */
332 hlim = wlim/min_ratio;
335 "size: center V: %dx%d in %dx%d (%.3f < %.3f < %.3f)\n",
336 wlim, hlim, it->xgwa.width, it->xgwa.height,
337 min_ratio, ratio, max_ratio);
342 height_diff = ((hlim + ANALOGTV_VISLINES/2) % ANALOGTV_VISLINES) - ANALOGTV_VISLINES/2;
343 if (height_diff != 0 && fabs(height_diff) < hlim * height_snap)
349 /* Most times this doesn't change */
350 if (wlim != oldwidth || hlim != oldheight) {
355 it->xrepl=1+it->usewidth/640;
356 if (it->xrepl>2) it->xrepl=2;
357 it->subwidth=it->usewidth/it->xrepl;
359 analogtv_free_image(it);
360 analogtv_alloc_image(it);
363 it->screen_xo = (it->xgwa.width-it->usewidth)/2;
364 it->screen_yo = (it->xgwa.height-it->useheight)/2;
369 analogtv_reconfigure(analogtv *it)
371 XGetWindowAttributes (it->dpy, it->window, &it->xgwa);
372 analogtv_configure(it);
376 analogtv_allocate(Display *dpy, Window window)
384 it=(analogtv *)calloc(1,sizeof(analogtv));
392 #ifdef HAVE_XSHM_EXTENSION
398 XGetWindowAttributes (it->dpy, it->window, &it->xgwa);
400 it->screen=it->xgwa.screen;
401 it->colormap=it->xgwa.colormap;
402 it->visclass=it->xgwa.visual->class;
403 it->visbits=it->xgwa.visual->bits_per_rgb;
404 it->visdepth=it->xgwa.depth;
405 if (it->visclass == TrueColor || it->visclass == DirectColor) {
406 if (get_integer_resource ("use_cmap", "Integer")) {
411 it->use_color=!mono_p;
413 else if (it->visclass == PseudoColor || it->visclass == StaticColor) {
415 it->use_color=!mono_p;
422 it->red_mask=it->xgwa.visual->red_mask;
423 it->green_mask=it->xgwa.visual->green_mask;
424 it->blue_mask=it->xgwa.visual->blue_mask;
425 it->red_shift=it->red_invprec=-1;
426 it->green_shift=it->green_invprec=-1;
427 it->blue_shift=it->blue_invprec=-1;
429 /* Is there a standard way to do this? Does this handle all cases? */
431 for (shift=0; shift<32; shift++) {
432 for (prec=1; prec<16 && prec<32-shift; prec++) {
433 unsigned long mask=(0xffffUL>>(16-prec)) << shift;
434 if (it->red_shift<0 && mask==it->red_mask)
435 it->red_shift=shift, it->red_invprec=16-prec;
436 if (it->green_shift<0 && mask==it->green_mask)
437 it->green_shift=shift, it->green_invprec=16-prec;
438 if (it->blue_shift<0 && mask==it->blue_mask)
439 it->blue_shift=shift, it->blue_invprec=16-prec;
442 if (it->red_shift<0 || it->green_shift<0 || it->blue_shift<0) {
443 if (0) fprintf(stderr,"Can't figure out color space\n");
447 for (i=0; i<ANALOGTV_CV_MAX; i++) {
448 int intensity=pow(i/256.0, 0.8)*65535.0; /* gamma correction */
449 if (intensity>65535) intensity=65535;
450 it->red_values[i]=((intensity>>it->red_invprec)<<it->red_shift);
451 it->green_values[i]=((intensity>>it->green_invprec)<<it->green_shift);
452 it->blue_values[i]=((intensity>>it->blue_invprec)<<it->blue_shift);
457 gcv.background=get_pixel_resource("background", "Background",
458 it->dpy, it->colormap);
460 it->gc = XCreateGC(it->dpy, it->window, GCBackground, &gcv);
461 XSetWindowBackground(it->dpy, it->window, gcv.background);
462 XClearWindow(dpy,window);
464 analogtv_configure(it);
474 analogtv_release(analogtv *it)
478 #ifdef HAVE_XSHM_EXTENSION
479 destroy_xshm_image(it->dpy, it->image, &it->shm_info);
482 XDestroyImage(it->image);
486 if (it->gc) XFreeGC(it->dpy, it->gc);
488 if (it->n_colors) XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L);
494 First generate the I and Q reference signals, which we'll multiply
495 the input signal by to accomplish the demodulation. Normally they
496 are shifted 33 degrees from the colorburst. I think this was convenient
497 for inductor-capacitor-vacuum tube implementation.
499 The tint control, FWIW, just adds a phase shift to the chroma signal,
500 and the color control controls the amplitude.
502 In text modes (colormode==0) the system disabled the color burst, and no
503 color was detected by the monitor.
505 freq_error gives a mismatch between the built-in oscillator and the
506 TV's colorbust. Some II Plus machines seemed to occasionally get
507 instability problems -- the crystal oscillator was a single
508 transistor if I remember correctly -- and the frequency would vary
509 enough that the tint would change across the width of the screen.
510 The left side would be in correct tint because it had just gotten
511 resynchronized with the color burst.
513 If we're using a colormap, set it up.
516 analogtv_set_demod(analogtv *it)
518 int y_levels=10,i_levels=5,q_levels=5;
521 In principle, we might be able to figure out how to adjust the
522 color map frame-by-frame to get some nice color bummage. But I'm
523 terrified of changing the color map because we'll get flashing.
525 I can hardly believe we still have to deal with colormaps. They're
526 like having NEAR PTRs: an enormous hassle for the programmer just
527 to save on memory. They should have been deprecated by 1995 or
531 if (it->use_cmap && !it->n_colors) {
534 XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L);
540 for (yli=0; yli<y_levels; yli++) {
541 for (ili=0; ili<i_levels; ili++) {
542 for (qli=0; qli<q_levels; qli++) {
543 double interpy,interpi,interpq;
544 double levelmult=700.0;
548 interpy=100.0 * ((double)yli/y_levels);
549 interpi=50.0 * (((double)ili-(0.5*i_levels))/(double)i_levels);
550 interpq=50.0 * (((double)qli-(0.5*q_levels))/(double)q_levels);
552 r=(int)((interpy + 1.04*interpi + 0.624*interpq)*levelmult);
553 g=(int)((interpy - 0.276*interpi - 0.639*interpq)*levelmult);
554 b=(int)((interpy - 1.105*interpi + 1.729*interpq)*levelmult);
556 if (r>65535) r=65535;
558 if (g>65535) g=65535;
560 if (b>65535) b=65535;
563 printf("%0.2f %0.2f %0.2f => %02x%02x%02x\n",
564 interpy, interpi, interpq,
572 if (!XAllocColor(it->dpy, it->colormap, &col)) {
573 if (q_levels > y_levels*4/12)
575 else if (i_levels > y_levels*5/12)
584 it->colors[it->n_colors++]=col.pixel;
589 it->cmap_y_levels=y_levels;
590 it->cmap_i_levels=i_levels;
591 it->cmap_q_levels=q_levels;
600 analogtv_line_signature(analogtv_input *input, int lineno)
603 char *origsignal=&input->signal[(lineno+input->vsync)
604 %ANALOGTV_V][input->line_hsync[lineno]];
608 for (i=0; i<ANALOGTV_PIC_LEN; i++) {
610 hash = hash + (hash<<17) + c;
613 hash += input->line_hsync[lineno];
616 hash += input->hashnoise_times[lineno];
625 /* Here we model the analog circuitry of an NTSC television.
626 Basically, it splits the signal into 3 signals: Y, I and Q. Y
627 corresponds to luminance, and you get it by low-pass filtering the
628 input signal to below 3.57 MHz.
630 I and Q are the in-phase and quadrature components of the 3.57 MHz
631 subcarrier. We get them by multiplying by cos(3.57 MHz*t) and
632 sin(3.57 MHz*t), and low-pass filtering. Because the eye has less
633 resolution in some colors than others, the I component gets
634 low-pass filtered at 1.5 MHz and the Q at 0.5 MHz. The I component
635 is approximately orange-blue, and Q is roughly purple-green. See
636 http://www.ntsc-tv.com for details.
638 We actually do an awful lot to the signal here. I suspect it would
639 make sense to wrap them all up together by calculating impulse
640 response and doing FFT convolutions.
645 analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
651 int phasecorr=(signal-it->rx_signal)&3;
652 struct analogtv_yiq_s *yiq;
654 double agclevel=it->agclevel;
655 double brightadd=it->brightness_control*100.0 - ANALOGTV_BLACK_LEVEL;
656 double delay[MAXDELAY+ANALOGTV_PIC_LEN], *dp;
661 double cb_i=(it->line_cb_phase[lineno][(2+phasecorr)&3]-
662 it->line_cb_phase[lineno][(0+phasecorr)&3])/16.0;
663 double cb_q=(it->line_cb_phase[lineno][(3+phasecorr)&3]-
664 it->line_cb_phase[lineno][(1+phasecorr)&3])/16.0;
666 colormode = (cb_i * cb_i + cb_q * cb_q) > 2.8;
669 double tint_i = -cos((103 + it->color_control)*3.1415926/180);
670 double tint_q = sin((103 + it->color_control)*3.1415926/180);
672 multiq2[0] = (cb_i*tint_i - cb_q*tint_q) * it->color_control;
673 multiq2[1] = (cb_q*tint_i + cb_i*tint_q) * it->color_control;
674 multiq2[2]=-multiq2[0];
675 multiq2[3]=-multiq2[1];
681 printf("multiq = [%0.3f %0.3f %0.3f %0.3f] ",
682 it->multiq[60],it->multiq[61],it->multiq[62],it->multiq[63]);
683 printf("it->line_cb_phase = [%0.3f %0.3f %0.3f %0.3f]\n",
684 it->line_cb_phase[lineno][0],it->line_cb_phase[lineno][1],
685 it->line_cb_phase[lineno][2],it->line_cb_phase[lineno][3]);
686 printf("multiq2 = [%0.3f %0.3f %0.3f %0.3f]\n",
687 multiq2[0],multiq2[1],multiq2[2],multiq2[3]);
691 dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
692 for (i=0; i<5; i++) dp[i]=0.0;
695 assert(end < ANALOGTV_PIC_LEN+10);
697 dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
698 for (i=0; i<24; i++) dp[i]=0.0;
699 for (i=start, yiq=it->yiq+start, sp=signal+start;
701 i++, dp--, yiq++, sp++) {
703 /* Now filter them. These are infinite impulse response filters
704 calculated by the script at
705 http://www-users.cs.york.ac.uk/~fisher/mkfilter. This is
706 fixed-point integer DSP, son. No place for wimps. We do it in
707 integer because you can count on integer being faster on most
708 CPUs. We care about speed because we need to recalculate every
709 time we blink text, and when we spew random bytes into screen
710 memory. This is roughly 16.16 fixed point arithmetic, but we
711 scale some filter values up by a few bits to avoid some nasty
714 /* Filter Y with a 4-pole low-pass Butterworth filter at 3.5 MHz
715 with an extra zero at 3.5 MHz, from
716 mkfilter -Bu -Lp -o 4 -a 2.1428571429e-01 0 -Z 2.5e-01 -l
719 dp[0] = sp[0] * 0.0469904257251935 * agclevel;
720 dp[8] = (+1.0*(dp[6]+dp[0])
726 yiq->y = dp[8] + brightadd;
730 dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
731 for (i=0; i<27; i++) dp[i]=0.0;
733 for (i=start, yiq=it->yiq+start, sp=signal+start;
735 i++, dp--, yiq++, sp++) {
738 /* Filter I and Q with a 3-pole low-pass Butterworth filter at
739 1.5 MHz with an extra zero at 3.5 MHz, from
740 mkfilter -Bu -Lp -o 3 -a 1.0714285714e-01 0 -Z 2.5000000000e-01 -l
744 dp[0] = sig*multiq2[i&3] * 0.0833333333333;
745 yiq->i=dp[8] = (dp[5] + dp[0]
748 -0.3333333333 * dp[10]);
750 dp[16] = sig*multiq2[(i+3)&3] * 0.0833333333333;
751 yiq->q=dp[24] = (dp[16+5] + dp[16+0]
752 +3.0*(dp[16+4] + dp[16+1])
753 +4.0*(dp[16+3] + dp[16+2])
754 -0.3333333333 * dp[24+2]);
757 for (i=start, yiq=it->yiq+start; i<end; i++, yiq++) {
758 yiq->i = yiq->q = 0.0;
764 analogtv_setup_teletext(analogtv_input *input)
767 int teletext=ANALOGTV_BLACK_LEVEL;
769 /* Teletext goes in line 21. But I suspect there are other things
770 in the vertical retrace interval */
772 for (y=19; y<22; y++) {
773 for (x=ANALOGTV_PIC_START; x<ANALOGTV_PIC_END; x++) {
775 teletext=(random()&1) ? ANALOGTV_WHITE_LEVEL : ANALOGTV_BLACK_LEVEL;
777 input->signal[y][x]=teletext;
783 analogtv_setup_frame(analogtv *it)
789 if (it->flutter_horiz_desync) {
790 /* Horizontal sync during vertical sync instability. */
791 it->horiz_desync += -0.10*(it->horiz_desync-3.0) +
792 ((int)(random()&0xff)-0x80) *
793 ((int)(random()&0xff)-0x80) *
794 ((int)(random()&0xff)-0x80) * 0.000001;
797 for (i=0; i<ANALOGTV_V; i++) {
798 it->hashnoise_times[i]=0;
801 if (it->hashnoise_enable && !it->hashnoise_on) {
802 if (random()%10000==0) {
804 it->shrinkpulse=random()%ANALOGTV_V;
807 if (random()%1000==0) {
810 if (it->hashnoise_on) {
811 it->hashnoise_rpm += (15000.0 - it->hashnoise_rpm)*0.05 +
812 ((int)(random()%2000)-1000)*0.1;
814 it->hashnoise_rpm -= 100 + 0.01*it->hashnoise_rpm;
815 if (it->hashnoise_rpm<0.0) it->hashnoise_rpm=0.0;
817 if (it->hashnoise_rpm > 0.0) {
819 int hnc=it->hashnoise_counter; /* in 24.8 format */
821 /* Convert rpm of a 16-pole motor into dots in 24.8 format */
822 hni = (int)(ANALOGTV_V * ANALOGTV_H * 256.0 /
823 (it->hashnoise_rpm * 16.0 / 60.0 / 60.0));
825 while (hnc < (ANALOGTV_V * ANALOGTV_H)<<8) {
826 y=(hnc>>8)/ANALOGTV_H;
827 x=(hnc>>8)%ANALOGTV_H;
829 if (x>0 && x<ANALOGTV_H - ANALOGTV_HASHNOISE_LEN) {
830 it->hashnoise_times[y]=x;
832 hnc += hni + (int)(random()%65536)-32768;
834 hnc -= (ANALOGTV_V * ANALOGTV_H)<<8;
837 if (it->rx_signal_level != 0.0)
838 it->agclevel = 1.0/it->rx_signal_level;
843 for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
844 printf(" %0.3f",it->ghostfir[i]);
846 printf(" siglevel=%g agc=%g\n", siglevel, it->agclevel);
851 analogtv_setup_sync(analogtv_input *input, int do_cb, int do_ssavi)
856 int synclevel = do_ssavi ? ANALOGTV_WHITE_LEVEL : ANALOGTV_SYNC_LEVEL;
858 for (lineno=0; lineno<ANALOGTV_V; lineno++) {
859 vsync=lineno>=3 && lineno<7;
861 sig=input->signal[lineno];
863 i=ANALOGTV_SYNC_START;
865 while (i<ANALOGTV_BP_START) sig[i++]=ANALOGTV_BLANK_LEVEL;
866 while (i<ANALOGTV_H) sig[i++]=synclevel;
868 while (i<ANALOGTV_BP_START) sig[i++]=synclevel;
869 while (i<ANALOGTV_PIC_START) sig[i++]=ANALOGTV_BLANK_LEVEL;
870 while (i<ANALOGTV_FP_START) sig[i++]=ANALOGTV_BLACK_LEVEL;
872 while (i<ANALOGTV_H) sig[i++]=ANALOGTV_BLANK_LEVEL;
875 /* 9 cycles of colorburst */
876 for (i=ANALOGTV_CB_START; i<ANALOGTV_CB_START+36; i+=4) {
877 sig[i+1] += ANALOGTV_CB_LEVEL;
878 sig[i+3] -= ANALOGTV_CB_LEVEL;
885 analogtv_sync(analogtv *it)
887 int cur_hsync=it->cur_hsync;
888 int cur_vsync=it->cur_vsync;
893 double cbfc=1.0/128.0;
895 sp = it->rx_signal + lineno*ANALOGTV_H + cur_hsync;
896 for (i=-32; i<32; i++) {
897 lineno = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V;
898 sp = it->rx_signal + lineno*ANALOGTV_H;
900 for (j=0; j<ANALOGTV_H; j+=ANALOGTV_H/16) {
903 filt *= it->agclevel;
905 osc = (double)(ANALOGTV_V+i)/(double)ANALOGTV_V;
907 if (osc >= 1.05+0.0002 * filt) break;
909 cur_vsync = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V;
911 for (lineno=0; lineno<ANALOGTV_V; lineno++) {
913 if (lineno>5 && lineno<ANALOGTV_V-3) { /* ignore vsync interval */
915 sp = it->rx_signal + ((lineno + cur_vsync + ANALOGTV_V)%ANALOGTV_V
916 )*ANALOGTV_H + cur_hsync;
917 for (i=-8; i<8; i++) {
918 osc = (double)(ANALOGTV_H+i)/(double)ANALOGTV_H;
919 filt=(sp[i-3]+sp[i-2]+sp[i-1]+sp[i]) * it->agclevel;
921 if (osc >= 1.005 + 0.0001*filt) break;
923 cur_hsync = (cur_hsync + i + ANALOGTV_H) % ANALOGTV_H;
926 it->line_hsync[lineno]=(cur_hsync + ANALOGTV_PIC_START +
927 ANALOGTV_H) % ANALOGTV_H;
929 /* Now look for the colorburst, which is a few cycles after the H
930 sync pulse, and store its phase.
931 The colorburst is 9 cycles long, and we look at the middle 5
936 sp = it->rx_signal + lineno*ANALOGTV_H + (cur_hsync&~3);
937 for (i=ANALOGTV_CB_START+8; i<ANALOGTV_CB_START+36-8; i++) {
938 it->cb_phase[i&3] = it->cb_phase[i&3]*(1.0-cbfc) +
939 sp[i]*it->agclevel*cbfc;
947 for (i=0; i<4; i++) {
948 tot += it->cb_phase[i]*it->cb_phase[i];
950 cbgain = 32.0/sqrt(tot);
952 for (i=0; i<4; i++) {
953 it->line_cb_phase[lineno][i]=it->cb_phase[i]*cbgain;
958 if (0) printf("hs=%d cb=[%0.3f %0.3f %0.3f %0.3f]\n",
960 it->cb_phase[0], it->cb_phase[1],
961 it->cb_phase[2], it->cb_phase[3]);
964 /* if (random()%2000==0) cur_hsync=random()%ANALOGTV_H; */
967 it->cur_hsync = cur_hsync;
968 it->cur_vsync = cur_vsync;
972 analogtv_levelmult(analogtv *it, int level)
974 static double levelfac[3]={-7.5, 5.5, 24.5};
975 return (40.0 + levelfac[level]*puramp(it, 3.0, 6.0, 1.0))/256.0;
979 analogtv_level(analogtv *it, int y, int ytop, int ybot)
983 if (y==ytop || y==ybot-1) level=0;
984 else if (y==ytop+1 || y==ybot-2) level=1;
987 else if (ybot-ytop>=5) {
988 if (y==ytop || y==ybot-1) level=0;
991 else if (ybot-ytop>=3) {
992 if (y==ytop) level=0;
1003 The point of this stuff is to ensure that when useheight is not a
1004 multiple of VISLINES so that TV scan lines map to different numbers
1005 of vertical screen pixels, the total brightness of each scan line
1007 MAX_LINEHEIGHT corresponds to 2400 vertical pixels, beyond which
1008 it interpolates extra black lines.
1010 enum {MAX_LINEHEIGHT=12};
1014 } leveltable[MAX_LINEHEIGHT+1][MAX_LINEHEIGHT+1];
1017 analogtv_setup_levels(analogtv *it, double avgheight)
1020 static double levelfac[3]={-7.5, 5.5, 24.5};
1022 for (height=0; height<avgheight+2.0 && height<=MAX_LINEHEIGHT; height++) {
1024 for (i=0; i<height; i++) {
1025 leveltable[height][i].index = 2;
1029 leveltable[height][0].index=0;
1032 leveltable[height][height-1].index=0;
1035 leveltable[height][1].index=1;
1036 leveltable[height][height-2].index=1;
1039 for (i=0; i<height; i++) {
1040 leveltable[height][i].value =
1041 (40.0 + levelfac[leveltable[height][i].index]*puramp(it, 3.0, 6.0, 1.0)) / 256.0;
1048 analogtv_blast_imagerow(analogtv *it,
1049 float *rgbf, float *rgbf_end,
1054 char *level_copyfrom[3];
1055 int xrepl=it->xrepl;
1056 for (i=0; i<3; i++) level_copyfrom[i]=NULL;
1058 for (y=ytop; y<ybot; y++) {
1059 int level=leveltable[ybot-ytop][y-ytop].index;
1060 double levelmult=leveltable[ybot-ytop][y-ytop].value;
1063 rowdata=it->image->data + y*it->image->bytes_per_line;
1065 /* Fast special cases to avoid the slow XPutPixel. Ugh. It goes to show
1066 why standard graphics sw has to be fast, or else people will have to
1067 work around it and risk incompatibility. The quickdraw folks
1068 understood this. The other answer would be for X11 to have fewer
1069 formats for bitm.. oh, never mind. If neither of these cases work
1070 (they probably cover 99% of setups) it falls back on the Xlib
1073 if (level_copyfrom[level]) {
1074 memcpy(rowdata, level_copyfrom[level], it->image->bytes_per_line);
1077 level_copyfrom[level] = rowdata;
1081 else if (it->image->format==ZPixmap &&
1082 it->image->bits_per_pixel==32 &&
1083 sizeof(unsigned int)==4 &&
1084 it->image->byte_order==localbyteorder) {
1085 /* int is more likely to be 32 bits than long */
1086 unsigned int *pixelptr=(unsigned int *)rowdata;
1089 for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) {
1090 int ntscri=rpf[0]*levelmult;
1091 int ntscgi=rpf[1]*levelmult;
1092 int ntscbi=rpf[2]*levelmult;
1093 if (ntscri>=ANALOGTV_CV_MAX) ntscri=ANALOGTV_CV_MAX-1;
1094 if (ntscgi>=ANALOGTV_CV_MAX) ntscgi=ANALOGTV_CV_MAX-1;
1095 if (ntscbi>=ANALOGTV_CV_MAX) ntscbi=ANALOGTV_CV_MAX-1;
1096 pix = (it->red_values[ntscri] |
1097 it->green_values[ntscgi] |
1098 it->blue_values[ntscbi]);
1102 if (xrepl>=3) pixelptr[2] = pix;
1107 else if (it->image->format==ZPixmap &&
1108 it->image->bits_per_pixel==16 &&
1109 sizeof(unsigned short)==2 &&
1110 float_extraction_works &&
1111 it->image->byte_order==localbyteorder) {
1112 unsigned short *pixelptr=(unsigned short *)rowdata;
1114 float_extract_t r1,g1,b1;
1117 for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) {
1118 r2=rpf[0]; g2=rpf[1]; b2=rpf[2];
1119 r1.f=r2 * levelmult+float_low8_ofs;
1120 g1.f=g2 * levelmult+float_low8_ofs;
1121 b1.f=b2 * levelmult+float_low8_ofs;
1122 pix = (it->red_values[r1.i & 0x3ff] |
1123 it->green_values[g1.i & 0x3ff] |
1124 it->blue_values[b1.i & 0x3ff]);
1128 if (xrepl>=3) pixelptr[2] = pix;
1133 else if (it->image->format==ZPixmap &&
1134 it->image->bits_per_pixel==16 &&
1135 sizeof(unsigned short)==2 &&
1136 it->image->byte_order==localbyteorder) {
1137 unsigned short *pixelptr=(unsigned short *)rowdata;
1140 for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) {
1141 int r1=rpf[0] * levelmult;
1142 int g1=rpf[1] * levelmult;
1143 int b1=rpf[2] * levelmult;
1144 if (r1>=ANALOGTV_CV_MAX) r1=ANALOGTV_CV_MAX-1;
1145 if (g1>=ANALOGTV_CV_MAX) g1=ANALOGTV_CV_MAX-1;
1146 if (b1>=ANALOGTV_CV_MAX) b1=ANALOGTV_CV_MAX-1;
1147 pix = it->red_values[r1] | it->green_values[g1] | it->blue_values[b1];
1151 if (xrepl>=3) pixelptr[2] = pix;
1157 for (x=0, rpf=rgbf; rpf!=rgbf_end ; x++, rpf+=3) {
1158 int ntscri=rpf[0]*levelmult;
1159 int ntscgi=rpf[1]*levelmult;
1160 int ntscbi=rpf[2]*levelmult;
1161 if (ntscri>=ANALOGTV_CV_MAX) ntscri=ANALOGTV_CV_MAX-1;
1162 if (ntscgi>=ANALOGTV_CV_MAX) ntscgi=ANALOGTV_CV_MAX-1;
1163 if (ntscbi>=ANALOGTV_CV_MAX) ntscbi=ANALOGTV_CV_MAX-1;
1164 for (j=0; j<xrepl; j++) {
1165 XPutPixel(it->image, x*xrepl + j, y,
1166 it->red_values[ntscri] | it->green_values[ntscgi] |
1167 it->blue_values[ntscbi]);
1176 analogtv_draw(analogtv *it)
1179 int scanstart_i,scanend_i,squishright_i,squishdiv,pixrate;
1180 float *rgb_start, *rgb_end;
1183 int bigloadchange,drawcount;
1186 int overall_top, overall_bot;
1188 float *raw_rgb_start=(float *)calloc(it->subwidth*3, sizeof(float));
1189 float *raw_rgb_end=raw_rgb_start+3*it->subwidth;
1192 analogtv_setup_frame(it);
1193 analogtv_set_demod(it);
1195 /* rx_signal has an extra 2 lines at the end, where we copy the
1196 first 2 lines so we can index into it while only worrying about
1197 wraparound on a per-line level */
1198 memcpy(&it->rx_signal[ANALOGTV_SIGNAL_LEN],
1200 2*ANALOGTV_H*sizeof(it->rx_signal[0]));
1205 /* if (it->hashnoise_on) baseload=0.5; */
1209 it->crtload[ANALOGTV_TOP-1]=baseload;
1210 puheight = puramp(it, 2.0, 1.0, 1.3) * it->height_control *
1211 (1.125 - 0.125*puramp(it, 2.0, 2.0, 1.1));
1213 analogtv_setup_levels(it, puheight * (double)it->useheight/(double)ANALOGTV_VISLINES);
1215 overall_top=it->useheight;
1218 for (lineno=ANALOGTV_TOP; lineno<ANALOGTV_BOT; lineno++) {
1219 int slineno=lineno-ANALOGTV_TOP;
1220 int ytop=(int)((slineno*it->useheight/ANALOGTV_VISLINES -
1221 it->useheight/2)*puheight) + it->useheight/2;
1222 int ybot=(int)(((slineno+1)*it->useheight/ANALOGTV_VISLINES -
1223 it->useheight/2)*puheight) + it->useheight/2;
1225 int linesig=analogtv_line_signature(input,lineno)
1226 + it->hashnoise_times[lineno];
1228 double *signal=(it->rx_signal + ((lineno + it->cur_vsync +
1229 ANALOGTV_V)%ANALOGTV_V) * ANALOGTV_H +
1230 it->line_hsync[lineno]);
1232 if (ytop==ybot) continue;
1233 if (ybot<0 || ytop>it->useheight) continue;
1235 if (ybot>it->useheight) ybot=it->useheight;
1237 if (ybot > ytop+MAX_LINEHEIGHT) ybot=ytop+MAX_LINEHEIGHT;
1239 if (ytop < overall_top) overall_top=ytop;
1240 if (ybot > overall_bot) overall_bot=ybot;
1242 if (lineno==it->shrinkpulse) {
1249 if (it->hashnoise_rpm>0.0 &&
1252 (slineno<20 && it->flutter_horiz_desync) ||
1253 it->gaussiannoise_level>30 ||
1254 ((it->gaussiannoise_level>2.0 ||
1255 it->multipath) && random()%4) ||
1256 linesig != it->onscreen_signature[lineno])) {
1259 it->onscreen_signature[lineno] = linesig;
1264 Interpolate the 600-dotclock line into however many horizontal
1265 screen pixels we're using, and convert to RGB.
1267 We add some 'bloom', variations in the horizontal scan width with
1268 the amount of brightness, extremely common on period TV sets. They
1269 had a single oscillator which generated both the horizontal scan and
1270 (during the horizontal retrace interval) the high voltage for the
1271 electron beam. More brightness meant more load on the oscillator,
1272 which caused an decrease in horizontal deflection. Look for
1275 Also, the A2 did a bad job of generating horizontal sync pulses
1276 during the vertical blanking interval. This, and the fact that the
1277 horizontal frequency was a bit off meant that TVs usually went a bit
1278 out of sync during the vertical retrace, and the top of the screen
1279 would be bent a bit to the left or right. Look for (shiftthisrow).
1281 We also add a teeny bit of left overscan, just enough to be
1282 annoying, but you can still read the left column of text.
1284 We also simulate compression & brightening on the right side of the
1285 screen. Most TVs do this, but you don't notice because they overscan
1286 so it's off the right edge of the CRT. But the A2 video system used
1287 so much of the horizontal scan line that you had to crank the
1288 horizontal width down in order to not lose the right few characters,
1289 and you'd see the compression on the right edge. Associated with
1290 compression is brightening; since the electron beam was scanning
1291 slower, the same drive signal hit the phosphor harder. Look for
1292 (squishright_i) and (squishdiv).
1299 for (i=0; i<ANALOGTV_PIC_LEN; i++) {
1300 totsignal += signal[i];
1302 totsignal *= it->agclevel;
1303 ncl = 0.95 * it->crtload[lineno-1] +
1305 (totsignal-30000)/100000.0 +
1306 (slineno>184 ? (slineno-184)*(lineno-184)*0.001 * it->squeezebottom
1308 diff=ncl - it->crtload[lineno];
1309 bigloadchange = (diff>0.01 || diff<-0.01);
1310 it->crtload[lineno]=ncl;
1314 double bloomthisrow,shiftthisrow;
1315 double viswidth,middle;
1319 bloomthisrow = -10.0 * it->crtload[lineno];
1320 if (bloomthisrow<-10.0) bloomthisrow=-10.0;
1321 if (bloomthisrow>2.0) bloomthisrow=2.0;
1323 shiftthisrow=it->horiz_desync * (exp(-0.17*slineno) *
1324 (0.7+cos(slineno*0.6)));
1329 viswidth=ANALOGTV_PIC_LEN * 0.79 - 5.0*bloomthisrow;
1330 middle=ANALOGTV_PIC_LEN/2 - shiftthisrow;
1332 scanwidth=it->width_control * puramp(it, 0.5, 0.3, 1.0);
1334 scw=it->subwidth*scanwidth;
1335 if (scw>it->subwidth) scw=it->usewidth;
1336 scl=it->subwidth/2 - scw/2;
1337 scr=it->subwidth/2 + scw/2;
1339 pixrate=(int)((viswidth*65536.0*1.0)/it->subwidth)/scanwidth;
1340 scanstart_i=(int)((middle-viswidth*0.5)*65536.0);
1341 scanend_i=(ANALOGTV_PIC_LEN-1)*65536;
1342 squishright_i=(int)((middle+viswidth*(0.25 + 0.25*puramp(it, 2.0, 0.0, 1.1)
1343 - it->squish_control)) *65536.0);
1344 squishdiv=it->subwidth/15;
1346 rgb_start=raw_rgb_start+scl*3;
1347 rgb_end=raw_rgb_start+scr*3;
1349 assert(scanstart_i>=0);
1352 if (0) printf("scan %d: %0.3f %0.3f %0.3f scl=%d scr=%d scw=%d\n",
1354 scanstart_i/65536.0,
1355 squishright_i/65536.0,
1362 for (y=ytop; y<ybot; y++) {
1363 int level=analogtv_level(it, y, ytop, ybot);
1364 double levelmult=analogtv_levelmult(it, level);
1365 double levelmult_y = levelmult * it->contrast_control
1366 * puramp(it, 1.0, 0.0, 1.0) / (0.5+0.5*puheight) * 0.070;
1367 double levelmult_iq = levelmult * 0.090;
1369 struct analogtv_yiq_s *yiq=it->yiq;
1370 analogtv_ntsc_to_yiq(it, lineno, signal,
1371 (scanstart_i>>16)-10, (scanend_i>>16)+10);
1376 while (i<0 && x<it->usewidth) {
1377 XPutPixel(it->image, x, y, it->colors[0]);
1382 while (i<scanend_i && x<it->usewidth) {
1383 double pixfrac=(i&0xffff)/65536.0;
1384 double invpixfrac=(1.0-pixfrac);
1386 int yli,ili,qli,cmi;
1388 double interpy=(yiq[pati].y*invpixfrac
1389 + yiq[pati+1].y*pixfrac) * levelmult_y;
1390 double interpi=(yiq[pati].i*invpixfrac
1391 + yiq[pati+1].i*pixfrac) * levelmult_iq;
1392 double interpq=(yiq[pati].q*invpixfrac
1393 + yiq[pati+1].q*pixfrac) * levelmult_iq;
1395 yli = (int)(interpy * it->cmap_y_levels);
1396 ili = (int)((interpi+0.5) * it->cmap_i_levels);
1397 qli = (int)((interpq+0.5) * it->cmap_q_levels);
1399 if (yli>=it->cmap_y_levels) yli=it->cmap_y_levels-1;
1401 if (ili>=it->cmap_i_levels) ili=it->cmap_i_levels-1;
1403 if (qli>=it->cmap_q_levels) qli=it->cmap_q_levels-1;
1405 cmi=qli + it->cmap_i_levels*(ili + it->cmap_q_levels*yli);
1408 if ((random()%65536)==0) {
1409 printf("%0.3f %0.3f %0.3f => %d %d %d => %d\n",
1410 interpy, interpi, interpq,
1416 for (j=0; j<it->xrepl; j++) {
1417 XPutPixel(it->image, x, y,
1421 if (i >= squishright_i) {
1422 pixmultinc += pixmultinc/squishdiv;
1426 while (x<it->usewidth) {
1427 XPutPixel(it->image, x, y, it->colors[0]);
1433 struct analogtv_yiq_s *yiq=it->yiq;
1434 analogtv_ntsc_to_yiq(it, lineno, signal,
1435 (scanstart_i>>16)-10, (scanend_i>>16)+10);
1437 pixbright=it->contrast_control * puramp(it, 1.0, 0.0, 1.0)
1438 / (0.5+0.5*puheight) * 1024.0/100.0;
1440 i=scanstart_i; rrp=rgb_start;
1441 while (i<0 && rrp!=rgb_end) {
1442 rrp[0]=rrp[1]=rrp[2]=0;
1446 while (i<scanend_i && rrp!=rgb_end) {
1447 double pixfrac=(i&0xffff)/65536.0;
1448 double invpixfrac=1.0-pixfrac;
1452 double interpy=(yiq[pati].y*invpixfrac + yiq[pati+1].y*pixfrac);
1453 double interpi=(yiq[pati].i*invpixfrac + yiq[pati+1].i*pixfrac);
1454 double interpq=(yiq[pati].q*invpixfrac + yiq[pati+1].q*pixfrac);
1457 According to the NTSC spec, Y,I,Q are generated as:
1459 y=0.30 r + 0.59 g + 0.11 b
1460 i=0.60 r - 0.28 g - 0.32 b
1461 q=0.21 r - 0.52 g + 0.31 b
1463 So if you invert the implied 3x3 matrix you get what standard
1464 televisions implement with a bunch of resistors (or directly in the
1467 r = y + 0.948 i + 0.624 q
1468 g = y - 0.276 i - 0.639 q
1469 b = y - 1.105 i + 1.729 q
1472 r=(interpy + 0.948*interpi + 0.624*interpq) * pixbright;
1473 g=(interpy - 0.276*interpi - 0.639*interpq) * pixbright;
1474 b=(interpy - 1.105*interpi + 1.729*interpq) * pixbright;
1482 if (i>=squishright_i) {
1483 pixmultinc += pixmultinc/squishdiv;
1484 pixbright += pixbright/squishdiv/2;
1489 while (rrp != rgb_end) {
1490 rrp[0]=rrp[1]=rrp[2]=0.0;
1494 analogtv_blast_imagerow(it, raw_rgb_start, raw_rgb_end,
1498 free(raw_rgb_start);
1501 /* poor attempt at visible retrace */
1502 for (i=0; i<15; i++) {
1503 int ytop=(int)((i*it->useheight/15 -
1504 it->useheight/2)*puheight) + it->useheight/2;
1505 int ybot=(int)(((i+1)*it->useheight/15 -
1506 it->useheight/2)*puheight) + it->useheight/2;
1507 int div=it->usewidth*3/2;
1509 for (x=0; x<it->usewidth; x++) {
1510 y = ytop + (ybot-ytop)*x / div;
1511 if (y<0 || y>=it->useheight) continue;
1512 XPutPixel(it->image, x, y, 0xffffff);
1517 if (it->need_clear) {
1518 XClearWindow(it->dpy, it->window);
1522 if (overall_top>0) {
1523 XClearArea(it->dpy, it->window,
1524 it->screen_xo, it->screen_yo,
1525 it->usewidth, overall_top, 0);
1527 if (it->useheight > overall_bot) {
1528 XClearArea(it->dpy, it->window,
1529 it->screen_xo, it->screen_yo+overall_bot,
1530 it->usewidth, it->useheight-overall_bot, 0);
1533 if (overall_bot > overall_top) {
1535 #ifdef HAVE_XSHM_EXTENSION
1536 XShmPutImage(it->dpy, it->window, it->gc, it->image,
1538 it->screen_xo, it->screen_yo+overall_top,
1539 it->usewidth, overall_bot - overall_top,
1543 XPutImage(it->dpy, it->window, it->gc, it->image,
1545 it->screen_xo, it->screen_yo+overall_top,
1546 it->usewidth, overall_bot - overall_top);
1555 gettimeofday(&tv,NULL);
1557 fps=1.0/((tv.tv_sec - it->last_display_time.tv_sec)
1558 + 0.000001*(tv.tv_usec - it->last_display_time.tv_usec));
1559 sprintf(buf, "FPS=%0.1f",fps);
1560 XDrawString(it->dpy, it->window, it->gc, 50, it->useheight*2/3,
1563 it->last_display_time=tv;
1571 analogtv_input_allocate()
1573 analogtv_input *ret=(analogtv_input *)calloc(1,sizeof(analogtv_input));
1579 This takes a screen image and encodes it as a video camera would,
1580 including all the bandlimiting and YIQ modulation.
1581 This isn't especially tuned for speed.
1585 analogtv_load_ximage(analogtv *it, analogtv_input *input, XImage *pic_im)
1592 XColor col1[ANALOGTV_PIC_LEN];
1593 XColor col2[ANALOGTV_PIC_LEN];
1594 int multiq[ANALOGTV_PIC_LEN+4];
1595 int y_overscan=5; /* overscan this much top and bottom */
1596 int y_scanlength=ANALOGTV_VISLINES+2*y_overscan;
1598 img_w=pic_im->width;
1599 img_h=pic_im->height;
1601 for (i=0; i<ANALOGTV_PIC_LEN+4; i++) {
1602 double phase=90.0-90.0*i;
1604 multiq[i]=(int)(-cos(3.1415926/180.0*(phase-303)) * 4096.0 * ampl);
1607 for (y=0; y<y_scanlength; y++) {
1608 int picy1=(y*img_h)/y_scanlength;
1609 int picy2=(y*img_h + y_scanlength/2)/y_scanlength;
1611 for (x=0; x<ANALOGTV_PIC_LEN; x++) {
1612 int picx=(x*img_w)/ANALOGTV_PIC_LEN;
1613 col1[x].pixel=XGetPixel(pic_im, picx, picy1);
1614 col2[x].pixel=XGetPixel(pic_im, picx, picy2);
1616 XQueryColors(it->dpy, it->colormap, col1, ANALOGTV_PIC_LEN);
1617 XQueryColors(it->dpy, it->colormap, col2, ANALOGTV_PIC_LEN);
1619 for (i=0; i<7; i++) fyx[i]=fyy[i]=0;
1620 for (i=0; i<4; i++) fix[i]=fiy[i]=fqx[i]=fqy[i]=0.0;
1622 for (x=0; x<ANALOGTV_PIC_LEN; x++) {
1624 int filty,filti,filtq;
1627 y=0.30 r + 0.59 g + 0.11 b
1628 i=0.60 r - 0.28 g - 0.32 b
1629 q=0.21 r - 0.52 g + 0.31 b
1630 The coefficients below are in .4 format */
1632 rawy=( 5*col1[x].red + 11*col1[x].green + 2*col1[x].blue +
1633 5*col2[x].red + 11*col2[x].green + 2*col2[x].blue)>>7;
1634 rawi=(10*col1[x].red - 4*col1[x].green - 5*col1[x].blue +
1635 10*col2[x].red - 4*col2[x].green - 5*col2[x].blue)>>7;
1636 rawq=( 3*col1[x].red - 8*col1[x].green + 5*col1[x].blue +
1637 3*col2[x].red - 8*col2[x].green + 5*col2[x].blue)>>7;
1639 /* Filter y at with a 4-pole low-pass Butterworth filter at 3.5 MHz
1640 with an extra zero at 3.5 MHz, from
1641 mkfilter -Bu -Lp -o 4 -a 2.1428571429e-01 0 -Z 2.5e-01 -l */
1643 fyx[0] = fyx[1]; fyx[1] = fyx[2]; fyx[2] = fyx[3];
1644 fyx[3] = fyx[4]; fyx[4] = fyx[5]; fyx[5] = fyx[6];
1645 fyx[6] = (rawy * 1897) >> 16;
1646 fyy[0] = fyy[1]; fyy[1] = fyy[2]; fyy[2] = fyy[3];
1647 fyy[3] = fyy[4]; fyy[4] = fyy[5]; fyy[5] = fyy[6];
1648 fyy[6] = (fyx[0]+fyx[6]) + 4*(fyx[1]+fyx[5]) + 7*(fyx[2]+fyx[4]) + 8*fyx[3]
1649 + ((-151*fyy[2] + 8115*fyy[3] - 38312*fyy[4] + 36586*fyy[5]) >> 16);
1652 /* Filter I at 1.5 MHz. 3 pole Butterworth from
1653 mkfilter -Bu -Lp -o 3 -a 1.0714285714e-01 0 */
1655 fix[0] = fix[1]; fix[1] = fix[2]; fix[2] = fix[3];
1656 fix[3] = (rawi * 1413) >> 16;
1657 fiy[0] = fiy[1]; fiy[1] = fiy[2]; fiy[2] = fiy[3];
1658 fiy[3] = (fix[0]+fix[3]) + 3*(fix[1]+fix[2])
1659 + ((16559*fiy[0] - 72008*fiy[1] + 109682*fiy[2]) >> 16);
1662 /* Filter Q at 0.5 MHz. 3 pole Butterworth from
1663 mkfilter -Bu -Lp -o 3 -a 3.5714285714e-02 0 -l */
1665 fqx[0] = fqx[1]; fqx[1] = fqx[2]; fqx[2] = fqx[3];
1666 fqx[3] = (rawq * 75) >> 16;
1667 fqy[0] = fqy[1]; fqy[1] = fqy[2]; fqy[2] = fqy[3];
1668 fqy[3] = (fqx[0]+fqx[3]) + 3 * (fqx[1]+fqx[2])
1669 + ((2612*fqy[0] - 9007*fqy[1] + 10453 * fqy[2]) >> 12);
1673 composite = filty + ((multiq[x] * filti + multiq[x+3] * filtq)>>12);
1674 composite = ((composite*100)>>14) + ANALOGTV_BLACK_LEVEL;
1675 if (composite>125) composite=125;
1676 if (composite<0) composite=0;
1677 input->signal[y-y_overscan+ANALOGTV_TOP][x+ANALOGTV_PIC_START] = composite;
1685 void analogtv_channel_noise(analogtv_input *it, analogtv_input *s2)
1688 int change=random()%ANALOGTV_V;
1689 unsigned int fastrnd=random();
1690 double hso=(int)(random()%1000)-500;
1691 int yofs=random()%ANALOGTV_V;
1694 for (y=change; y<ANALOGTV_V; y++) {
1695 int s2y=(y+yofs)%ANALOGTV_V;
1697 int noiselevel=60000 / (y-change+100);
1699 it->line_hsync[y] = s2->line_hsync[y] + (int)hso;
1701 for (x=0; x<ANALOGTV_H; x++) {
1703 filt+= (-filt/16) + (int)(fastrnd&0xfff)-0x800;
1704 noise=(filt*noiselevel)>>16;
1705 newsig=s2->signal[s2y][x] + noise;
1706 if (newsig>120) newsig=120;
1707 if (newsig<0) newsig=0;
1708 it->signal[y][x]=newsig;
1716 void analogtv_add_signal(analogtv *it, analogtv_reception *rec)
1718 analogtv_input *inp=rec->input;
1719 double *ps=it->rx_signal;
1720 double *pe=it->rx_signal + ANALOGTV_SIGNAL_LEN;
1722 char *ss=&inp->signal[0][0];
1723 char *se=&inp->signal[0][0] + ANALOGTV_SIGNAL_LEN;
1724 char *s=ss + ((unsigned)rec->ofs % ANALOGTV_SIGNAL_LEN);
1726 int ec=it->channel_change_cycles;
1727 double level=rec->level;
1728 double hfloss=rec->hfloss;
1729 unsigned int fastrnd=random();
1732 /* assert((se-ss)%4==0 && (se-s)%4==0); */
1734 /* duplicate the first line into the Nth line to ease wraparound computation */
1735 memcpy(inp->signal[ANALOGTV_V], inp->signal[0],
1736 ANALOGTV_H * sizeof(inp->signal[0][0]));
1738 for (i=0; i<8; i++) dp[i]=0.0;
1743 /* Do a big noisy transition. We can make the transition noise of
1744 high constant strength regardless of signal strength.
1746 There are two separate state machines. here, One is the noise
1747 process and the other is the
1749 We don't bother with the FIR filter here
1754 while (p!=pe && ec>0) {
1756 double sig0=(double)s[0];
1757 double noise = ((int)fastrnd-(int)0x7fffffff) * (50.0/(double)0x7fffffff);
1758 fastrnd = (fastrnd*1103515245+12345) & 0xffffffffu;
1760 p[0] += sig0 * level * (1.0 - noise_ampl) + noise * noise_ampl;
1762 noise_ampl *= 0.99995;
1773 double sig0,sig1,sig2,sig3,sigr;
1780 dp[0]=sig0+sig1+sig2+sig3;
1782 /* Get the video out signal, and add some ghosting, typical of RF
1783 monitor cables. This corresponds to a pretty long cable, but
1787 sigr=(dp[1]*rec->ghostfir[0] + dp[2]*rec->ghostfir[1] +
1788 dp[3]*rec->ghostfir[2] + dp[4]*rec->ghostfir[3]);
1789 dp[4]=dp[3]; dp[3]=dp[2]; dp[2]=dp[1]; dp[1]=dp[0];
1791 p[0] += (sig0+sigr + sig2*hfloss) * level;
1792 p[1] += (sig1+sigr + sig3*hfloss) * level;
1793 p[2] += (sig2+sigr + sig0*hfloss) * level;
1794 p[3] += (sig3+sigr + sig1*hfloss) * level;
1798 if (s>=se) s = ss + (s-se);
1801 it->rx_signal_level =
1802 sqrt(it->rx_signal_level * it->rx_signal_level +
1803 (level * level * (1.0 + 4.0*(rec->ghostfir[0] + rec->ghostfir[1] +
1804 rec->ghostfir[2] + rec->ghostfir[3]))));
1807 it->channel_change_cycles=0;
1813 if (it->hashnoise_times[lineno]) {
1814 int hnt=it->hashnoise_times[lineno] - input->line_hsync[lineno];
1816 if (hnt>=0 && hnt<ANALOGTV_PIC_LEN) {
1818 double cur=frand(150.0)-20.0;
1819 int len=random()%15+3;
1820 if (len > ANALOGTV_PIC_LEN-hnt) len=ANALOGTV_PIC_LEN-hnt;
1821 for (i=0; i<len; i++) {
1822 double sig=signal[hnt];
1825 cur += frand(5.0)-5.0;
1826 maxampl = maxampl*0.9;
1836 void analogtv_init_signal(analogtv *it, double noiselevel)
1838 double *ps=it->rx_signal;
1839 double *pe=it->rx_signal + ANALOGTV_SIGNAL_LEN;
1841 unsigned int fastrnd=random();
1842 double nm1=0.0,nm2=0.0;
1843 double noisemul = sqrt(noiselevel*150)/(double)0x7fffffff;
1847 nm1 = ((int)fastrnd-(int)0x7fffffff) * noisemul;
1849 fastrnd = (fastrnd*1103515245+12345) & 0xffffffffu;
1852 it->rx_signal_level = noiselevel;
1856 analogtv_reception_update(analogtv_reception *rec)
1860 if (rec->multipath > 0.0) {
1861 for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
1862 rec->ghostfir2[i] +=
1863 -(rec->ghostfir2[i]/16.0) + rec->multipath * (frand(0.02)-0.01);
1865 if (random()%20==0) {
1866 rec->ghostfir2[random()%(ANALOGTV_GHOSTFIR_LEN)]
1867 = rec->multipath * (frand(0.08)-0.04);
1869 for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
1870 rec->ghostfir[i] = 0.8*rec->ghostfir[i] + 0.2*rec->ghostfir2[i];
1874 rec->hfloss2 += -(rec->hfloss2/16.0) + rec->multipath * (frand(0.08)-0.04);
1875 rec->hfloss = 0.5*rec->hfloss + 0.5*rec->hfloss2;
1879 for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
1880 rec->ghostfir[i] = (i>=ANALOGTV_GHOSTFIR_LEN/2) ? ((i&1) ? +0.04 : -0.08) /ANALOGTV_GHOSTFIR_LEN
1888 analogtv_make_font(Display *dpy, Window window, analogtv_font *f,
1889 int w, int h, char *fontname)
1896 XWindowAttributes xgwa;
1901 XGetWindowAttributes (dpy, window, &xgwa);
1905 font = XLoadQueryFont (dpy, fontname);
1907 fprintf(stderr, "analogtv: can't load font %s\n", fontname);
1911 text_pm=XCreatePixmap(dpy, window, 128*f->char_w, f->char_h, xgwa.depth);
1913 memset(&gcv, 0, sizeof(gcv));
1917 gc=XCreateGC(dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
1919 XSetForeground(dpy, gc, 0);
1920 XFillRectangle(dpy, text_pm, gc, 0, 0, 128*f->char_w, f->char_h);
1921 XSetForeground(dpy, gc, 1);
1923 for (i=0; i<128; i++) {
1925 int x=f->char_w*i+1;
1926 int y=f->char_h*8/10;
1927 XDrawString(dpy, text_pm, gc, x, y, &c, 1);
1929 f->text_im = XGetImage(dpy, text_pm, 0, 0, 128*f->char_w, f->char_h,
1932 XFreePixmap(dpy, text_pm);
1934 f->text_im = XCreateImage(dpy, xgwa.visual, xgwa.depth,
1936 128*f->char_w, f->char_h, 8, 0);
1937 f->text_im->data = (char *)calloc(f->text_im->height,
1938 f->text_im->bytes_per_line);
1946 analogtv_font_pixel(analogtv_font *f, int c, int x, int y)
1948 if (x<0 || x>=f->char_w) return 0;
1949 if (y<0 || y>=f->char_h) return 0;
1950 if (c<0 || c>=128) return 0;
1951 return XGetPixel(f->text_im, c*f->char_w + x, y) ? 1 : 0;
1955 analogtv_font_set_pixel(analogtv_font *f, int c, int x, int y, int value)
1957 if (x<0 || x>=f->char_w) return;
1958 if (y<0 || y>=f->char_h) return;
1959 if (c<0 || c>=128) return;
1961 XPutPixel(f->text_im, c*f->char_w + x, y, value);
1965 analogtv_font_set_char(analogtv_font *f, int c, char *s)
1969 if (c<0 || c>=128) return;
1971 for (y=0; y<f->char_h; y++) {
1972 for (x=0; x<f->char_w; x++) {
1974 value=(*s==' ') ? 0 : 1;
1975 analogtv_font_set_pixel(f, c, x, y, value);
1982 analogtv_lcp_to_ntsc(double luma, double chroma, double phase, int ntsc[4])
1985 for (i=0; i<4; i++) {
1986 double w=90.0*i + phase;
1987 double val=luma + chroma * (cos(3.1415926/180.0*w));
1988 if (val<0.0) val=0.0;
1989 if (val>127.0) val=127.0;
1995 analogtv_draw_solid(analogtv_input *input,
1996 int left, int right, int top, int bot,
2001 if (right-left<4) right=left+4;
2002 if (bot-top<1) bot=top+1;
2004 for (y=top; y<bot; y++) {
2005 for (x=left; x<right; x++) {
2006 input->signal[y][x] = ntsc[x&3];
2013 analogtv_draw_solid_rel_lcp(analogtv_input *input,
2014 double left, double right, double top, double bot,
2015 double luma, double chroma, double phase)
2019 int topi=(int)(ANALOGTV_TOP + ANALOGTV_VISLINES*top);
2020 int boti=(int)(ANALOGTV_TOP + ANALOGTV_VISLINES*bot);
2021 int lefti=(int)(ANALOGTV_VIS_START + ANALOGTV_VIS_LEN*left);
2022 int righti=(int)(ANALOGTV_VIS_START + ANALOGTV_VIS_LEN*right);
2024 analogtv_lcp_to_ntsc(luma, chroma, phase, ntsc);
2025 analogtv_draw_solid(input, lefti, righti, topi, boti, ntsc);
2030 analogtv_draw_char(analogtv_input *input, analogtv_font *f,
2031 int c, int x, int y, int ntsc[4])
2033 int yc,xc,ys,xs,pix;
2035 for (yc=0; yc<f->char_h; yc++) {
2036 for (ys=y + yc*f->y_mult; ys<y + (yc+1)*f->y_mult; ys++) {
2037 if (ys<0 || ys>=ANALOGTV_V) continue;
2039 for (xc=0; xc<f->char_w; xc++) {
2040 pix=analogtv_font_pixel(f, c, xc, yc);
2042 for (xs=x + xc*f->x_mult; xs<x + (xc+1)*f->x_mult; xs++) {
2043 if (xs<0 || xs>=ANALOGTV_H) continue;
2045 input->signal[ys][xs] = ntsc[xs&3];
2054 analogtv_draw_string(analogtv_input *input, analogtv_font *f,
2055 char *s, int x, int y, int ntsc[4])
2058 analogtv_draw_char(input, f, *s, x, y, ntsc);
2065 analogtv_draw_string_centered(analogtv_input *input, analogtv_font *f,
2066 char *s, int x, int y, int ntsc[4])
2068 int width=strlen(s) * f->char_w * 4;
2071 analogtv_draw_string(input, f, s, x, y, ntsc);
2075 static const char hextonib[128] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2076 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2077 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2078 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
2079 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
2080 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2081 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
2082 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
2085 Much of this function was adapted from logo.c
2088 analogtv_draw_xpm(analogtv *tv, analogtv_input *input,
2089 const char * const *xpm, int left, int top)
2094 int ncolors, nbytes;
2097 int r; int g; int b;
2101 if (4 != sscanf ((const char *) *xpm,
2103 &xpmw, &xpmh, &ncolors, &nbytes, &dummyc))
2105 if (ncolors < 1 || ncolors > 255)
2107 if (nbytes != 1) /* a serious limitation */
2111 for (i = 0; i < ncolors; i++) {
2112 const char *line = *xpm;
2113 int colori = ((unsigned char)*line++)&0xff;
2118 while (*line == ' ' || *line == '\t')
2121 if (which != 'c' && which != 'm')
2123 while (*line == ' ' || *line == '\t')
2125 if (!strncasecmp(line, "None", 4))
2134 r = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
2136 g = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
2138 b = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
2153 for (y=0; y<xpmh; y++) {
2154 const char *line = *xpm++;
2156 if (tvy<ANALOGTV_TOP || tvy>=ANALOGTV_BOT) continue;
2158 for (x=0; x<xpmw; x++) {
2159 int cbyte=((unsigned char)line[x])&0xff;
2162 if (tvx<ANALOGTV_PIC_START || tvx+4>ANALOGTV_PIC_END) continue;
2164 rawy=( 5*cmap[cbyte].r + 11*cmap[cbyte].g + 2*cmap[cbyte].b) / 64;
2165 rawi=(10*cmap[cbyte].r - 4*cmap[cbyte].g - 5*cmap[cbyte].b) / 64;
2166 rawq=( 3*cmap[cbyte].r - 8*cmap[cbyte].g + 5*cmap[cbyte].b) / 64;
2173 for (i=0; i<4; i++) {
2174 if (ntsc[i]>ANALOGTV_WHITE_LEVEL) ntsc[i]=ANALOGTV_WHITE_LEVEL;
2175 if (ntsc[i]<ANALOGTV_BLACK_LEVEL) ntsc[i]=ANALOGTV_BLACK_LEVEL;
2178 input->signal[tvy][tvx+0]= ntsc[(tvx+0)&3];
2179 input->signal[tvy][tvx+1]= ntsc[(tvx+1)&3];
2180 input->signal[tvy][tvx+2]= ntsc[(tvx+2)&3];
2181 input->signal[tvy][tvx+3]= ntsc[(tvx+3)&3];
2186 extern XtAppContext app;
2189 analogtv_handle_events (analogtv *it)
2191 XSync(it->dpy, False);
2192 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
2193 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
2195 while (XPending (it->dpy))
2198 XNextEvent (it->dpy, &event);
2199 switch (event.xany.type)
2209 if (it->key_handler) {
2210 if (it->key_handler (it->dpy, &event, it->key_data))
2213 XLookupString (&event.xkey, &c, 1, &keysym, 0);
2214 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
2220 /* I don't seem to get an event when clicking the "full
2221 screen" window manager icon, at least when using
2222 metacity. Thus, it doesn't change the video size. Is this
2223 some separate WM_* message I have to deal with?
2225 case ConfigureNotify:
2226 if (event.xconfigure.width != it->xgwa.width ||
2227 event.xconfigure.height != it->xgwa.height)
2228 analogtv_reconfigure(it);
2232 case GraphicsExpose:
2239 if (it->event_handler) {
2240 (*it->event_handler) (it->dpy, &event);