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>
54 #include <X11/Xutil.h>
55 #include <X11/Intrinsic.h>
58 #include "resources.h"
61 #include "grabscreen.h"
66 /* only works on linux + freebsd */
67 #include <machine/cpufunc.h>
69 #define DTIME_DECL u_int64_t dtimes[100]; int n_dtimes
70 #define DTIME_START do {n_dtimes=0; dtimes[n_dtimes++]=rdtsc(); } while (0)
71 #define DTIME dtimes[n_dtimes++]=rdtsc()
72 #define DTIME_SHOW(DIV) \
74 double _dtime_div=(DIV); \
75 printf("time/%.1f: ",_dtime_div); \
76 for (i=1; i<n_dtimes; i++) \
77 printf(" %0.9f",(dtimes[i]-dtimes[i-1])* 1e-9 / _dtime_div); \
84 #define DTIME_START do { } while (0)
85 #define DTIME do { } while (0)
86 #define DTIME_SHOW(DIV) do { } while (0)
91 #define FASTRND (fastrnd = fastrnd*1103515245+12345)
93 static void analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
96 static double puramp(analogtv *it, double tc, double start, double over)
98 double pt=it->powerup-start;
100 if (pt<0.0) return 0.0;
101 if (pt>900.0 || pt/tc>8.0) return 1.0;
103 ret=(1.0-exp(-pt/tc))*over;
104 if (ret>1.0) return 1.0;
109 There are actual standards for TV signals: NTSC and RS-170A describe the
110 system used in the US and Japan. Europe has slightly different systems, but
111 not different enough to make substantially different screensaver displays.
112 Sadly, the standards bodies don't do anything so useful as publish the spec on
113 the web. Best bets are:
115 http://www.ee.washington.edu/conselec/CE/kuhn/ntsc/95x4.htm
116 http://www.ntsc-tv.com/ntsc-index-02.htm
118 In DirectColor or TrueColor modes, it generates pixel values directly from RGB
119 values it calculates across each scan line. In PseudoColor mode, it consider
120 each possible pattern of 5 preceding bit values in each possible position
121 modulo 4 and allocates a color for each. A few things, like the brightening on
122 the right side as the horizontal trace slows down, aren't done in PseudoColor.
124 I'd like to add a bit of visible retrace, but it conflicts with being able to
125 bitcopy the image when fast scrolling. After another couple of CPU
126 generations, we could probably regenerate the whole image from scratch every
127 time. On a P4 2 GHz it can manage this fine for blinking text, but scrolling
131 /* localbyteorder is MSBFirst or LSBFirst */
132 static int localbyteorder;
133 static const double float_low8_ofs=8388608.0;
134 static int float_extraction_works;
146 unsigned int localbyteorder_loc = (MSBFirst<<24) | (LSBFirst<<0);
147 localbyteorder=*(char *)&localbyteorder_loc;
154 float_extraction_works=1;
155 for (i=0; i<256*4; i++) {
156 fe.f=float_low8_ofs+(double)i;
160 printf("Float extraction failed for %d => %d\n",i,ans);
162 float_extraction_works=0;
171 analogtv_set_defaults(analogtv *it, char *prefix)
175 sprintf(buf,"%sTVTint",prefix);
176 it->tint_control = get_float_resource(buf,"TVTint");
177 sprintf(buf,"%sTVColor",prefix);
178 it->color_control = get_float_resource(buf,"TVColor")/100.0;
179 sprintf(buf,"%sTVBrightness",prefix);
180 it->brightness_control = get_float_resource(buf,"TVBrightness") / 100.0;
181 sprintf(buf,"%sTVContrast",prefix);
182 it->contrast_control = get_float_resource(buf,"TVContrast") / 100.0;
183 it->height_control = 1.0;
184 it->width_control = 1.0;
185 it->squish_control = 0.0;
190 it->hashnoise_enable=1;
192 it->horiz_desync=frand(10.0)-5.0;
193 it->squeezebottom=frand(5.0)-1.0;
196 printf("analogtv: prefix=%s\n",prefix);
197 printf(" use: shm=%d cmap=%d color=%d\n",
198 it->use_shm,it->use_cmap,it->use_color);
199 printf(" controls: tint=%g color=%g brightness=%g contrast=%g\n",
200 it->tint_control, it->color_control, it->brightness_control,
201 it->contrast_control);
202 printf(" freq_error %g: %g %d\n",
203 it->freq_error, it->freq_error_inc, it->flutter_tint);
204 printf(" desync: %g %d\n",
205 it->horiz_desync, it->flutter_horiz_desync);
206 printf(" hashnoise rpm: %g\n",
208 printf(" vis: %d %d %d\n",
209 it->visclass, it->visbits, it->visdepth);
210 printf(" shift: %d-%d %d-%d %d-%d\n",
211 it->red_invprec,it->red_shift,
212 it->green_invprec,it->green_shift,
213 it->blue_invprec,it->blue_shift);
214 printf(" size: %d %d %d %d xrepl=%d\n",
215 it->usewidth, it->useheight,
216 it->screen_xo, it->screen_yo, it->xrepl);
218 printf(" ANALOGTV_V=%d\n",ANALOGTV_V);
219 printf(" ANALOGTV_TOP=%d\n",ANALOGTV_TOP);
220 printf(" ANALOGTV_VISLINES=%d\n",ANALOGTV_VISLINES);
221 printf(" ANALOGTV_BOT=%d\n",ANALOGTV_BOT);
222 printf(" ANALOGTV_H=%d\n",ANALOGTV_H);
223 printf(" ANALOGTV_SYNC_START=%d\n",ANALOGTV_SYNC_START);
224 printf(" ANALOGTV_BP_START=%d\n",ANALOGTV_BP_START);
225 printf(" ANALOGTV_CB_START=%d\n",ANALOGTV_CB_START);
226 printf(" ANALOGTV_PIC_START=%d\n",ANALOGTV_PIC_START);
227 printf(" ANALOGTV_PIC_LEN=%d\n",ANALOGTV_PIC_LEN);
228 printf(" ANALOGTV_FP_START=%d\n",ANALOGTV_FP_START);
229 printf(" ANALOGTV_PIC_END=%d\n",ANALOGTV_PIC_END);
230 printf(" ANALOGTV_HASHNOISE_LEN=%d\n",ANALOGTV_HASHNOISE_LEN);
236 extern Bool mono_p; /* shoot me */
239 analogtv_free_image(analogtv *it)
243 #ifdef HAVE_XSHM_EXTENSION
244 destroy_xshm_image(it->dpy, it->image, &it->shm_info);
247 XDestroyImage(it->image);
254 analogtv_alloc_image(analogtv *it)
257 #ifdef HAVE_XSHM_EXTENSION
258 it->image=create_xshm_image(it->dpy, it->xgwa.visual, it->xgwa.depth, ZPixmap, 0,
259 &it->shm_info, it->usewidth, it->useheight);
261 if (!it->image) it->use_shm=0;
264 it->image = XCreateImage(it->dpy, it->xgwa.visual, it->xgwa.depth, ZPixmap, 0, 0,
265 it->usewidth, it->useheight, 8, 0);
266 it->image->data = (char *)calloc(it->image->height, it->image->bytes_per_line);
272 analogtv_configure(analogtv *it)
274 int oldwidth=it->usewidth;
275 int oldheight=it->useheight;
278 hlim=it->xgwa.height;
279 if (hlim<ANALOGTV_VISLINES) hlim = ANALOGTV_VISLINES;
281 wlim = it->xgwa.width;
282 if (wlim<300) wlim = 300;
284 /* require 3:4 aspect ratio */
285 if (wlim > hlim*4/3) wlim=hlim*4/3;
286 if (hlim > wlim*3/4) hlim=wlim*3/4;
288 /* height must be a multiple of VISLINES */
290 hlim = (hlim/ANALOGTV_VISLINES)*ANALOGTV_VISLINES;
292 /* Scale width proportionally */
293 wlim=wlim*hlim/ohlim;
296 FILE *fp=fopen("/tmp/analogtv.size","w");
297 fprintf(fp,"wlim=%d hlim=%d\n", wlim, hlim);
301 /* Most times this doesn't change */
302 if (wlim != oldwidth || hlim != oldheight) {
307 it->xrepl=1+it->usewidth/640;
308 if (it->xrepl>2) it->xrepl=2;
309 it->subwidth=it->usewidth/it->xrepl;
311 analogtv_free_image(it);
312 analogtv_alloc_image(it);
315 it->screen_xo = (it->xgwa.width-it->usewidth)/2;
316 it->screen_yo = (it->xgwa.height-it->useheight)/2;
321 analogtv_reconfigure(analogtv *it)
323 XGetWindowAttributes (it->dpy, it->window, &it->xgwa);
324 analogtv_configure(it);
328 analogtv_allocate(Display *dpy, Window window)
336 it=(analogtv *)calloc(1,sizeof(analogtv));
344 #ifdef HAVE_XSHM_EXTENSION
350 XGetWindowAttributes (it->dpy, it->window, &it->xgwa);
352 it->screen=it->xgwa.screen;
353 it->colormap=it->xgwa.colormap;
354 it->visclass=it->xgwa.visual->class;
355 it->visbits=it->xgwa.visual->bits_per_rgb;
356 it->visdepth=it->xgwa.depth;
357 if (it->visclass == TrueColor || it->visclass == DirectColor) {
358 if (get_integer_resource ("use_cmap", "Integer")) {
363 it->use_color=!mono_p;
365 else if (it->visclass == PseudoColor || it->visclass == StaticColor) {
367 it->use_color=!mono_p;
374 it->red_mask=it->xgwa.visual->red_mask;
375 it->green_mask=it->xgwa.visual->green_mask;
376 it->blue_mask=it->xgwa.visual->blue_mask;
377 it->red_shift=it->red_invprec=-1;
378 it->green_shift=it->green_invprec=-1;
379 it->blue_shift=it->blue_invprec=-1;
381 /* Is there a standard way to do this? Does this handle all cases? */
383 for (shift=0; shift<32; shift++) {
384 for (prec=1; prec<16 && prec<32-shift; prec++) {
385 unsigned long mask=(0xffffUL>>(16-prec)) << shift;
386 if (it->red_shift<0 && mask==it->red_mask)
387 it->red_shift=shift, it->red_invprec=16-prec;
388 if (it->green_shift<0 && mask==it->green_mask)
389 it->green_shift=shift, it->green_invprec=16-prec;
390 if (it->blue_shift<0 && mask==it->blue_mask)
391 it->blue_shift=shift, it->blue_invprec=16-prec;
394 if (it->red_shift<0 || it->green_shift<0 || it->blue_shift<0) {
395 if (0) fprintf(stderr,"Can't figure out color space\n");
399 for (i=0; i<ANALOGTV_CV_MAX; i++) {
400 int intensity=pow(i/256.0, 0.8)*65535.0; /* gamma correction */
401 if (intensity>65535) intensity=65535;
402 it->red_values[i]=((intensity>>it->red_invprec)<<it->red_shift);
403 it->green_values[i]=((intensity>>it->green_invprec)<<it->green_shift);
404 it->blue_values[i]=((intensity>>it->blue_invprec)<<it->blue_shift);
409 gcv.background=get_pixel_resource("background", "Background",
410 it->dpy, it->colormap);
412 it->gc = XCreateGC(it->dpy, it->window, GCBackground, &gcv);
413 XSetWindowBackground(it->dpy, it->window, gcv.background);
414 XClearWindow(dpy,window);
416 analogtv_configure(it);
426 analogtv_release(analogtv *it)
430 #ifdef HAVE_XSHM_EXTENSION
431 destroy_xshm_image(it->dpy, it->image, &it->shm_info);
434 XDestroyImage(it->image);
438 if (it->gc) XFreeGC(it->dpy, it->gc);
440 if (it->n_colors) XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L);
446 First generate the I and Q reference signals, which we'll multiply
447 the input signal by to accomplish the demodulation. Normally they
448 are shifted 33 degrees from the colorburst. I think this was convenient
449 for inductor-capacitor-vacuum tube implementation.
451 The tint control, FWIW, just adds a phase shift to the chroma signal,
452 and the color control controls the amplitude.
454 In text modes (colormode==0) the system disabled the color burst, and no
455 color was detected by the monitor.
457 freq_error gives a mismatch between the built-in oscillator and the
458 TV's colorbust. Some II Plus machines seemed to occasionally get
459 instability problems -- the crystal oscillator was a single
460 transistor if I remember correctly -- and the frequency would vary
461 enough that the tint would change across the width of the screen.
462 The left side would be in correct tint because it had just gotten
463 resynchronized with the color burst.
465 If we're using a colormap, set it up.
468 analogtv_set_demod(analogtv *it)
470 int y_levels=10,i_levels=5,q_levels=5;
473 In principle, we might be able to figure out how to adjust the
474 color map frame-by-frame to get some nice color bummage. But I'm
475 terrified of changing the color map because we'll get flashing.
477 I can hardly believe we still have to deal with colormaps. They're
478 like having NEAR PTRs: an enormous hassle for the programmer just
479 to save on memory. They should have been deprecated by 1995 or
483 if (it->use_cmap && !it->n_colors) {
486 XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L);
492 for (yli=0; yli<y_levels; yli++) {
493 for (ili=0; ili<i_levels; ili++) {
494 for (qli=0; qli<q_levels; qli++) {
495 double interpy,interpi,interpq;
496 double levelmult=700.0;
500 interpy=100.0 * ((double)yli/y_levels);
501 interpi=50.0 * (((double)ili-(0.5*i_levels))/(double)i_levels);
502 interpq=50.0 * (((double)qli-(0.5*q_levels))/(double)q_levels);
504 r=(int)((interpy + 1.04*interpi + 0.624*interpq)*levelmult);
505 g=(int)((interpy - 0.276*interpi - 0.639*interpq)*levelmult);
506 b=(int)((interpy - 1.105*interpi + 1.729*interpq)*levelmult);
508 if (r>65535) r=65535;
510 if (g>65535) g=65535;
512 if (b>65535) b=65535;
515 printf("%0.2f %0.2f %0.2f => %02x%02x%02x\n",
516 interpy, interpi, interpq,
524 if (!XAllocColor(it->dpy, it->colormap, &col)) {
525 if (q_levels > y_levels*4/12)
527 else if (i_levels > y_levels*5/12)
536 it->colors[it->n_colors++]=col.pixel;
541 it->cmap_y_levels=y_levels;
542 it->cmap_i_levels=i_levels;
543 it->cmap_q_levels=q_levels;
552 analogtv_line_signature(analogtv_input *input, int lineno)
555 char *origsignal=&input->signal[(lineno+input->vsync)
556 %ANALOGTV_V][input->line_hsync[lineno]];
560 for (i=0; i<ANALOGTV_PIC_LEN; i++) {
562 hash = hash + (hash<<17) + c;
565 hash += input->line_hsync[lineno];
568 hash += input->hashnoise_times[lineno];
577 /* Here we model the analog circuitry of an NTSC television.
578 Basically, it splits the signal into 3 signals: Y, I and Q. Y
579 corresponds to luminance, and you get it by low-pass filtering the
580 input signal to below 3.57 MHz.
582 I and Q are the in-phase and quadrature components of the 3.57 MHz
583 subcarrier. We get them by multiplying by cos(3.57 MHz*t) and
584 sin(3.57 MHz*t), and low-pass filtering. Because the eye has less
585 resolution in some colors than others, the I component gets
586 low-pass filtered at 1.5 MHz and the Q at 0.5 MHz. The I component
587 is approximately orange-blue, and Q is roughly purple-green. See
588 http://www.ntsc-tv.com for details.
590 We actually do an awful lot to the signal here. I suspect it would
591 make sense to wrap them all up together by calculating impulse
592 response and doing FFT convolutions.
597 analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal,
603 int phasecorr=(signal-it->rx_signal)&3;
604 struct analogtv_yiq_s *yiq;
606 double agclevel=it->agclevel;
607 double brightadd=it->brightness_control*100.0 - ANALOGTV_BLACK_LEVEL;
608 double delay[MAXDELAY+ANALOGTV_PIC_LEN], *dp;
613 double cb_i=(it->line_cb_phase[lineno][(2+phasecorr)&3]-
614 it->line_cb_phase[lineno][(0+phasecorr)&3])/16.0;
615 double cb_q=(it->line_cb_phase[lineno][(3+phasecorr)&3]-
616 it->line_cb_phase[lineno][(1+phasecorr)&3])/16.0;
618 colormode = (cb_i * cb_i + cb_q * cb_q) > 2.8;
621 double tint_i = -cos((103 + it->color_control)*3.1415926/180);
622 double tint_q = sin((103 + it->color_control)*3.1415926/180);
624 multiq2[0] = (cb_i*tint_i - cb_q*tint_q) * it->color_control;
625 multiq2[1] = (cb_q*tint_i + cb_i*tint_q) * it->color_control;
626 multiq2[2]=-multiq2[0];
627 multiq2[3]=-multiq2[1];
633 printf("multiq = [%0.3f %0.3f %0.3f %0.3f] ",
634 it->multiq[60],it->multiq[61],it->multiq[62],it->multiq[63]);
635 printf("it->line_cb_phase = [%0.3f %0.3f %0.3f %0.3f]\n",
636 it->line_cb_phase[lineno][0],it->line_cb_phase[lineno][1],
637 it->line_cb_phase[lineno][2],it->line_cb_phase[lineno][3]);
638 printf("multiq2 = [%0.3f %0.3f %0.3f %0.3f]\n",
639 multiq2[0],multiq2[1],multiq2[2],multiq2[3]);
643 dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
644 for (i=0; i<5; i++) dp[i]=0.0;
647 assert(end < ANALOGTV_PIC_LEN+10);
649 dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
650 for (i=0; i<24; i++) dp[i]=0.0;
651 for (i=start, yiq=it->yiq+start, sp=signal+start;
653 i++, dp--, yiq++, sp++) {
655 /* Now filter them. These are infinite impulse response filters
656 calculated by the script at
657 http://www-users.cs.york.ac.uk/~fisher/mkfilter. This is
658 fixed-point integer DSP, son. No place for wimps. We do it in
659 integer because you can count on integer being faster on most
660 CPUs. We care about speed because we need to recalculate every
661 time we blink text, and when we spew random bytes into screen
662 memory. This is roughly 16.16 fixed point arithmetic, but we
663 scale some filter values up by a few bits to avoid some nasty
666 /* Filter Y with a 4-pole low-pass Butterworth filter at 3.5 MHz
667 with an extra zero at 3.5 MHz, from
668 mkfilter -Bu -Lp -o 4 -a 2.1428571429e-01 0 -Z 2.5e-01 -l
671 dp[0] = sp[0] * 0.0469904257251935 * agclevel;
672 dp[8] = (+1.0*(dp[6]+dp[0])
678 yiq->y = dp[8] + brightadd;
682 dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
683 for (i=0; i<27; i++) dp[i]=0.0;
685 for (i=start, yiq=it->yiq+start, sp=signal+start;
687 i++, dp--, yiq++, sp++) {
690 /* Filter I and Q with a 3-pole low-pass Butterworth filter at
691 1.5 MHz with an extra zero at 3.5 MHz, from
692 mkfilter -Bu -Lp -o 3 -a 1.0714285714e-01 0 -Z 2.5000000000e-01 -l
696 dp[0] = sig*multiq2[i&3] * 0.0833333333333;
697 yiq->i=dp[8] = (dp[5] + dp[0]
700 -0.3333333333 * dp[10]);
702 dp[16] = sig*multiq2[(i+3)&3] * 0.0833333333333;
703 yiq->q=dp[24] = (dp[16+5] + dp[16+0]
704 +3.0*(dp[16+4] + dp[16+1])
705 +4.0*(dp[16+3] + dp[16+2])
706 -0.3333333333 * dp[24+2]);
709 for (i=start, yiq=it->yiq+start; i<end; i++, yiq++) {
710 yiq->i = yiq->q = 0.0;
716 analogtv_setup_teletext(analogtv_input *input)
719 int teletext=ANALOGTV_BLACK_LEVEL;
721 /* Teletext goes in line 21. But I suspect there are other things
722 in the vertical retrace interval */
724 for (y=19; y<22; y++) {
725 for (x=ANALOGTV_PIC_START; x<ANALOGTV_PIC_END; x++) {
727 teletext=(random()&1) ? ANALOGTV_WHITE_LEVEL : ANALOGTV_BLACK_LEVEL;
729 input->signal[y][x]=teletext;
735 analogtv_setup_frame(analogtv *it)
741 if (it->flutter_horiz_desync) {
742 /* Horizontal sync during vertical sync instability. */
743 it->horiz_desync += -0.10*(it->horiz_desync-3.0) +
744 ((int)(random()&0xff)-0x80) *
745 ((int)(random()&0xff)-0x80) *
746 ((int)(random()&0xff)-0x80) * 0.000001;
749 for (i=0; i<ANALOGTV_V; i++) {
750 it->hashnoise_times[i]=0;
753 if (it->hashnoise_enable && !it->hashnoise_on) {
754 if (random()%10000==0) {
756 it->shrinkpulse=random()%ANALOGTV_V;
759 if (random()%1000==0) {
762 if (it->hashnoise_on) {
763 it->hashnoise_rpm += (15000.0 - it->hashnoise_rpm)*0.05 +
764 ((int)(random()%2000)-1000)*0.1;
766 it->hashnoise_rpm -= 100 + 0.01*it->hashnoise_rpm;
767 if (it->hashnoise_rpm<0.0) it->hashnoise_rpm=0.0;
769 if (it->hashnoise_rpm >= 0.0) {
771 int hnc=it->hashnoise_counter; /* in 24.8 format */
773 /* Convert rpm of a 16-pole motor into dots in 24.8 format */
774 hni = (int)(ANALOGTV_V * ANALOGTV_H * 256.0 /
775 (it->hashnoise_rpm * 16.0 / 60.0 / 60.0));
777 while (hnc < (ANALOGTV_V * ANALOGTV_H)<<8) {
778 y=(hnc>>8)/ANALOGTV_H;
779 x=(hnc>>8)%ANALOGTV_H;
781 if (x>0 && x<ANALOGTV_H - ANALOGTV_HASHNOISE_LEN) {
782 it->hashnoise_times[y]=x;
784 hnc += hni + (int)(random()%65536)-32768;
786 hnc -= (ANALOGTV_V * ANALOGTV_H)<<8;
789 it->agclevel = 1.0/it->rx_signal_level;
794 for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
795 printf(" %0.3f",it->ghostfir[i]);
797 printf(" siglevel=%g agc=%g\n", siglevel, it->agclevel);
802 analogtv_setup_sync(analogtv_input *input, int do_cb, int do_ssavi)
807 int synclevel = do_ssavi ? ANALOGTV_WHITE_LEVEL : ANALOGTV_SYNC_LEVEL;
809 for (lineno=0; lineno<ANALOGTV_V; lineno++) {
810 vsync=lineno>=3 && lineno<7;
812 sig=input->signal[lineno];
814 i=ANALOGTV_SYNC_START;
816 while (i<ANALOGTV_BP_START) sig[i++]=ANALOGTV_BLANK_LEVEL;
817 while (i<ANALOGTV_H) sig[i++]=synclevel;
819 while (i<ANALOGTV_BP_START) sig[i++]=synclevel;
820 while (i<ANALOGTV_PIC_START) sig[i++]=ANALOGTV_BLANK_LEVEL;
821 while (i<ANALOGTV_FP_START) sig[i++]=ANALOGTV_BLACK_LEVEL;
823 while (i<ANALOGTV_H) sig[i++]=ANALOGTV_BLANK_LEVEL;
826 /* 9 cycles of colorburst */
827 for (i=ANALOGTV_CB_START; i<ANALOGTV_CB_START+36; i+=4) {
828 sig[i+1] += ANALOGTV_CB_LEVEL;
829 sig[i+3] -= ANALOGTV_CB_LEVEL;
836 analogtv_sync(analogtv *it)
838 int cur_hsync=it->cur_hsync;
839 int cur_vsync=it->cur_vsync;
844 double cbfc=1.0/128.0;
846 sp = it->rx_signal + lineno*ANALOGTV_H + cur_hsync;
847 for (i=-32; i<32; i++) {
848 lineno = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V;
849 sp = it->rx_signal + lineno*ANALOGTV_H;
851 for (j=0; j<ANALOGTV_H; j+=ANALOGTV_H/16) {
854 filt *= it->agclevel;
856 osc = (double)(ANALOGTV_V+i)/(double)ANALOGTV_V;
858 if (osc >= 1.05+0.0002 * filt) break;
860 cur_vsync = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V;
862 for (lineno=0; lineno<ANALOGTV_V; lineno++) {
864 if (lineno>5 && lineno<ANALOGTV_V-3) { /* ignore vsync interval */
866 sp = it->rx_signal + ((lineno + cur_vsync + ANALOGTV_V)%ANALOGTV_V
867 )*ANALOGTV_H + cur_hsync;
868 for (i=-8; i<8; i++) {
869 osc = (double)(ANALOGTV_H+i)/(double)ANALOGTV_H;
870 filt=(sp[i-3]+sp[i-2]+sp[i-1]+sp[i]) * it->agclevel;
872 if (osc >= 1.005 + 0.0001*filt) break;
874 cur_hsync = (cur_hsync + i + ANALOGTV_H) % ANALOGTV_H;
877 it->line_hsync[lineno]=(cur_hsync + ANALOGTV_PIC_START +
878 ANALOGTV_H) % ANALOGTV_H;
880 /* Now look for the colorburst, which is a few cycles after the H
881 sync pulse, and store its phase.
882 The colorburst is 9 cycles long, and we look at the middle 5
887 sp = it->rx_signal + lineno*ANALOGTV_H + (cur_hsync&~3);
888 for (i=ANALOGTV_CB_START+8; i<ANALOGTV_CB_START+36-8; i++) {
889 it->cb_phase[i&3] = it->cb_phase[i&3]*(1.0-cbfc) +
890 sp[i]*it->agclevel*cbfc;
898 for (i=0; i<4; i++) {
899 tot += it->cb_phase[i]*it->cb_phase[i];
901 cbgain = 32.0/sqrt(tot);
903 for (i=0; i<4; i++) {
904 it->line_cb_phase[lineno][i]=it->cb_phase[i]*cbgain;
909 if (0) printf("hs=%d cb=[%0.3f %0.3f %0.3f %0.3f]\n",
911 it->cb_phase[0], it->cb_phase[1],
912 it->cb_phase[2], it->cb_phase[3]);
915 /* if (random()%2000==0) cur_hsync=random()%ANALOGTV_H; */
918 it->cur_hsync = cur_hsync;
919 it->cur_vsync = cur_vsync;
923 analogtv_levelmult(analogtv *it, int level)
925 static double levelfac[3]={-7.5, 5.5, 24.5};
926 return (40.0 + levelfac[level]*puramp(it, 3.0, 6.0, 1.0))/256.0;
930 analogtv_level(analogtv *it, int y, int ytop, int ybot)
934 if (y==ytop || y==ybot-1) level=0;
935 else if (y==ytop+1 || y==ybot-2) level=1;
938 else if (ybot-ytop>=5) {
939 if (y==ytop || y==ybot-1) level=0;
942 else if (ybot-ytop>=3) {
943 if (y==ytop) level=0;
953 analogtv_blast_imagerow(analogtv *it,
954 float *rgbf, float *rgbf_end,
959 char *level_copyfrom[3];
961 for (i=0; i<3; i++) level_copyfrom[i]=NULL;
963 for (y=ytop; y<ybot; y++) {
964 int level=analogtv_level(it, y, ytop, ybot);
967 rowdata=it->image->data + y*it->image->bytes_per_line;
969 /* Fast special cases to avoid the slow XPutPixel. Ugh. It goes to show
970 why standard graphics sw has to be fast, or else people will have to
971 work around it and risk incompatibility. The quickdraw folks
972 understood this. The other answer would be for X11 to have fewer
973 formats for bitm.. oh, never mind. If neither of these cases work
974 (they probably cover 99% of setups) it falls back on the Xlib
977 if (level_copyfrom[level]) {
978 memcpy(rowdata, level_copyfrom[level], it->image->bytes_per_line);
981 double levelmult=analogtv_levelmult(it, level);
982 level_copyfrom[level] = rowdata;
986 else if (it->image->format==ZPixmap &&
987 it->image->bits_per_pixel==32 &&
988 sizeof(unsigned int)==4 &&
989 it->image->byte_order==localbyteorder) {
990 /* int is more likely to be 32 bits than long */
991 unsigned int *pixelptr=(unsigned int *)rowdata;
994 for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) {
995 int ntscri=rpf[0]*levelmult;
996 int ntscgi=rpf[1]*levelmult;
997 int ntscbi=rpf[2]*levelmult;
998 if (ntscri>=ANALOGTV_CV_MAX) ntscri=ANALOGTV_CV_MAX-1;
999 if (ntscgi>=ANALOGTV_CV_MAX) ntscgi=ANALOGTV_CV_MAX-1;
1000 if (ntscbi>=ANALOGTV_CV_MAX) ntscbi=ANALOGTV_CV_MAX-1;
1001 pix = (it->red_values[ntscri] |
1002 it->green_values[ntscgi] |
1003 it->blue_values[ntscbi]);
1007 if (xrepl>=3) pixelptr[2] = pix;
1012 else if (it->image->format==ZPixmap &&
1013 it->image->bits_per_pixel==16 &&
1014 sizeof(unsigned short)==2 &&
1015 float_extraction_works &&
1016 it->image->byte_order==localbyteorder) {
1017 unsigned short *pixelptr=(unsigned short *)rowdata;
1019 float_extract_t r1,g1,b1;
1022 for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) {
1023 r2=rpf[0]; g2=rpf[1]; b2=rpf[2];
1024 r1.f=r2 * levelmult+float_low8_ofs;
1025 g1.f=g2 * levelmult+float_low8_ofs;
1026 b1.f=b2 * levelmult+float_low8_ofs;
1027 pix = (it->red_values[r1.i & 0x3ff] |
1028 it->green_values[g1.i & 0x3ff] |
1029 it->blue_values[b1.i & 0x3ff]);
1033 if (xrepl>=3) pixelptr[2] = pix;
1038 else if (it->image->format==ZPixmap &&
1039 it->image->bits_per_pixel==16 &&
1040 sizeof(unsigned short)==2 &&
1041 it->image->byte_order==localbyteorder) {
1042 unsigned short *pixelptr=(unsigned short *)rowdata;
1045 for (rpf=rgbf; rpf!=rgbf_end; rpf+=3) {
1046 int r1=rpf[0] * levelmult;
1047 int g1=rpf[1] * levelmult;
1048 int b1=rpf[2] * levelmult;
1049 if (r1>=ANALOGTV_CV_MAX) r1=ANALOGTV_CV_MAX-1;
1050 if (g1>=ANALOGTV_CV_MAX) g1=ANALOGTV_CV_MAX-1;
1051 if (b1>=ANALOGTV_CV_MAX) b1=ANALOGTV_CV_MAX-1;
1052 pix = it->red_values[r1] | it->green_values[g1] | it->blue_values[b1];
1056 if (xrepl>=3) pixelptr[2] = pix;
1062 for (x=0, rpf=rgbf; rpf!=rgbf_end ; x++, rpf+=3) {
1063 int ntscri=rpf[0]*levelmult;
1064 int ntscgi=rpf[1]*levelmult;
1065 int ntscbi=rpf[2]*levelmult;
1066 if (ntscri>=ANALOGTV_CV_MAX) ntscri=ANALOGTV_CV_MAX-1;
1067 if (ntscgi>=ANALOGTV_CV_MAX) ntscgi=ANALOGTV_CV_MAX-1;
1068 if (ntscbi>=ANALOGTV_CV_MAX) ntscbi=ANALOGTV_CV_MAX-1;
1069 for (j=0; j<xrepl; j++) {
1070 XPutPixel(it->image, x*xrepl + j, y,
1071 it->red_values[ntscri] | it->green_values[ntscgi] |
1072 it->blue_values[ntscbi]);
1081 analogtv_draw(analogtv *it)
1084 int scanstart_i,scanend_i,squishright_i,squishdiv,pixrate;
1085 float *rgb_start, *rgb_end;
1088 int bigloadchange,drawcount;
1091 int overall_top, overall_bot;
1093 float *raw_rgb_start=(float *)calloc(it->subwidth*3, sizeof(float));
1094 float *raw_rgb_end=raw_rgb_start+3*it->subwidth;
1097 analogtv_setup_frame(it);
1098 analogtv_set_demod(it);
1100 /* rx_signal has an extra 2 lines at the end, where we copy the
1101 first 2 lines so we can index into it while only worrying about
1102 wraparound on a per-line level */
1103 memcpy(&it->rx_signal[ANALOGTV_SIGNAL_LEN],
1105 2*ANALOGTV_H*sizeof(it->rx_signal[0]));
1110 /* if (it->hashnoise_on) baseload=0.5; */
1114 it->crtload[ANALOGTV_TOP-1]=baseload;
1115 puheight = puramp(it, 2.0, 1.0, 1.3) * it->height_control *
1116 (1.125 - 0.125*puramp(it, 2.0, 2.0, 1.1));
1118 overall_top=it->useheight;
1121 for (lineno=ANALOGTV_TOP; lineno<ANALOGTV_BOT; lineno++) {
1122 int slineno=lineno-ANALOGTV_TOP;
1123 int ytop=(int)((slineno*it->useheight/ANALOGTV_VISLINES -
1124 it->useheight/2)*puheight) + it->useheight/2;
1125 int ybot=(int)(((slineno+1)*it->useheight/ANALOGTV_VISLINES -
1126 it->useheight/2)*puheight) + it->useheight/2;
1128 int linesig=analogtv_line_signature(input,lineno)
1129 + it->hashnoise_times[lineno];
1131 double *signal=(it->rx_signal + ((lineno + it->cur_vsync +
1132 ANALOGTV_V)%ANALOGTV_V) * ANALOGTV_H +
1133 it->line_hsync[lineno]);
1135 if (ytop==ybot) continue;
1136 if (ybot<0 || ytop>it->useheight) continue;
1138 if (ybot>it->useheight) ybot=it->useheight;
1140 if (ytop < overall_top) overall_top=ytop;
1141 if (ybot > overall_bot) overall_bot=ybot;
1143 if (lineno==it->shrinkpulse) {
1150 if (it->hashnoise_rpm>0.0 &&
1153 (slineno<20 && it->flutter_horiz_desync) ||
1154 it->gaussiannoise_level>30 ||
1155 ((it->gaussiannoise_level>2.0 ||
1156 it->multipath) && random()%4) ||
1157 linesig != it->onscreen_signature[lineno])) {
1160 it->onscreen_signature[lineno] = linesig;
1165 Interpolate the 600-dotclock line into however many horizontal
1166 screen pixels we're using, and convert to RGB.
1168 We add some 'bloom', variations in the horizontal scan width with
1169 the amount of brightness, extremely common on period TV sets. They
1170 had a single oscillator which generated both the horizontal scan and
1171 (during the horizontal retrace interval) the high voltage for the
1172 electron beam. More brightness meant more load on the oscillator,
1173 which caused an decrease in horizontal deflection. Look for
1176 Also, the A2 did a bad job of generating horizontal sync pulses
1177 during the vertical blanking interval. This, and the fact that the
1178 horizontal frequency was a bit off meant that TVs usually went a bit
1179 out of sync during the vertical retrace, and the top of the screen
1180 would be bent a bit to the left or right. Look for (shiftthisrow).
1182 We also add a teeny bit of left overscan, just enough to be
1183 annoying, but you can still read the left column of text.
1185 We also simulate compression & brightening on the right side of the
1186 screen. Most TVs do this, but you don't notice because they overscan
1187 so it's off the right edge of the CRT. But the A2 video system used
1188 so much of the horizontal scan line that you had to crank the
1189 horizontal width down in order to not lose the right few characters,
1190 and you'd see the compression on the right edge. Associated with
1191 compression is brightening; since the electron beam was scanning
1192 slower, the same drive signal hit the phosphor harder. Look for
1193 (squishright_i) and (squishdiv).
1200 for (i=0; i<ANALOGTV_PIC_LEN; i++) {
1201 totsignal += signal[i];
1203 totsignal *= it->agclevel;
1204 ncl = 0.95 * it->crtload[lineno-1] +
1206 (totsignal-30000)/100000.0 +
1207 (slineno>184 ? (slineno-184)*(lineno-184)*0.001 * it->squeezebottom
1209 diff=ncl - it->crtload[lineno];
1210 bigloadchange = (diff>0.01 || diff<-0.01);
1211 it->crtload[lineno]=ncl;
1215 double bloomthisrow,shiftthisrow;
1216 double viswidth,middle;
1220 bloomthisrow = -10.0 * it->crtload[lineno];
1221 if (bloomthisrow<-10.0) bloomthisrow=-10.0;
1222 if (bloomthisrow>2.0) bloomthisrow=2.0;
1224 shiftthisrow=it->horiz_desync * (exp(-0.17*slineno) *
1225 (0.7+cos(slineno*0.6)));
1230 viswidth=ANALOGTV_PIC_LEN * 0.79 - 5.0*bloomthisrow;
1231 middle=ANALOGTV_PIC_LEN/2 - shiftthisrow;
1233 scanwidth=it->width_control * puramp(it, 0.5, 0.3, 1.0);
1235 scw=it->subwidth*scanwidth;
1236 if (scw>it->subwidth) scw=it->usewidth;
1237 scl=it->subwidth/2 - scw/2;
1238 scr=it->subwidth/2 + scw/2;
1240 pixrate=(int)((viswidth*65536.0*1.0)/it->subwidth)/scanwidth;
1241 scanstart_i=(int)((middle-viswidth*0.5)*65536.0);
1242 scanend_i=(ANALOGTV_PIC_LEN-1)*65536;
1243 squishright_i=(int)((middle+viswidth*(0.25 + 0.25*puramp(it, 2.0, 0.0, 1.1)
1244 - it->squish_control)) *65536.0);
1245 squishdiv=it->subwidth/15;
1247 rgb_start=raw_rgb_start+scl*3;
1248 rgb_end=raw_rgb_start+scr*3;
1250 assert(scanstart_i>=0);
1253 if (0) printf("scan %d: %0.3f %0.3f %0.3f scl=%d scr=%d scw=%d\n",
1255 scanstart_i/65536.0,
1256 squishright_i/65536.0,
1263 for (y=ytop; y<ybot; y++) {
1264 int level=analogtv_level(it, y, ytop, ybot);
1265 double levelmult=analogtv_levelmult(it, level);
1266 double levelmult_y = levelmult * it->contrast_control
1267 * puramp(it, 1.0, 0.0, 1.0) / (0.5+0.5*puheight) * 0.070;
1268 double levelmult_iq = levelmult * 0.090;
1270 struct analogtv_yiq_s *yiq=it->yiq;
1271 analogtv_ntsc_to_yiq(it, lineno, signal,
1272 (scanstart_i>>16)-10, (scanend_i>>16)+10);
1277 while (i<0 && x<it->usewidth) {
1278 XPutPixel(it->image, x, y, it->colors[0]);
1283 while (i<scanend_i && x<it->usewidth) {
1284 double pixfrac=(i&0xffff)/65536.0;
1285 double invpixfrac=(1.0-pixfrac);
1287 int yli,ili,qli,cmi;
1289 double interpy=(yiq[pati].y*invpixfrac
1290 + yiq[pati+1].y*pixfrac) * levelmult_y;
1291 double interpi=(yiq[pati].i*invpixfrac
1292 + yiq[pati+1].i*pixfrac) * levelmult_iq;
1293 double interpq=(yiq[pati].q*invpixfrac
1294 + yiq[pati+1].q*pixfrac) * levelmult_iq;
1296 yli = (int)(interpy * it->cmap_y_levels);
1297 ili = (int)((interpi+0.5) * it->cmap_i_levels);
1298 qli = (int)((interpq+0.5) * it->cmap_q_levels);
1300 if (yli>=it->cmap_y_levels) yli=it->cmap_y_levels-1;
1302 if (ili>=it->cmap_i_levels) ili=it->cmap_i_levels-1;
1304 if (qli>=it->cmap_q_levels) qli=it->cmap_q_levels-1;
1306 cmi=qli + it->cmap_i_levels*(ili + it->cmap_q_levels*yli);
1309 if ((random()%65536)==0) {
1310 printf("%0.3f %0.3f %0.3f => %d %d %d => %d\n",
1311 interpy, interpi, interpq,
1317 for (j=0; j<it->xrepl; j++) {
1318 XPutPixel(it->image, x, y,
1322 if (i >= squishright_i) {
1323 pixmultinc += pixmultinc/squishdiv;
1327 while (x<it->usewidth) {
1328 XPutPixel(it->image, x, y, it->colors[0]);
1334 struct analogtv_yiq_s *yiq=it->yiq;
1335 analogtv_ntsc_to_yiq(it, lineno, signal,
1336 (scanstart_i>>16)-10, (scanend_i>>16)+10);
1338 pixbright=it->contrast_control * puramp(it, 1.0, 0.0, 1.0)
1339 / (0.5+0.5*puheight) * 1024.0/100.0;
1341 i=scanstart_i; rrp=rgb_start;
1342 while (i<0 && rrp!=rgb_end) {
1343 rrp[0]=rrp[1]=rrp[2]=0;
1347 while (i<scanend_i && rrp!=rgb_end) {
1348 double pixfrac=(i&0xffff)/65536.0;
1349 double invpixfrac=1.0-pixfrac;
1353 double interpy=(yiq[pati].y*invpixfrac + yiq[pati+1].y*pixfrac);
1354 double interpi=(yiq[pati].i*invpixfrac + yiq[pati+1].i*pixfrac);
1355 double interpq=(yiq[pati].q*invpixfrac + yiq[pati+1].q*pixfrac);
1358 According to the NTSC spec, Y,I,Q are generated as:
1360 y=0.30 r + 0.59 g + 0.11 b
1361 i=0.60 r - 0.28 g - 0.32 b
1362 q=0.21 r - 0.52 g + 0.31 b
1364 So if you invert the implied 3x3 matrix you get what standard
1365 televisions implement with a bunch of resistors (or directly in the
1368 r = y + 0.948 i + 0.624 q
1369 g = y - 0.276 i - 0.639 q
1370 b = y - 1.105 i + 1.729 q
1373 r=(interpy + 0.948*interpi + 0.624*interpq) * pixbright;
1374 g=(interpy - 0.276*interpi - 0.639*interpq) * pixbright;
1375 b=(interpy - 1.105*interpi + 1.729*interpq) * pixbright;
1383 if (i>=squishright_i) {
1384 pixmultinc += pixmultinc/squishdiv;
1385 pixbright += pixbright/squishdiv/2;
1390 while (rrp != rgb_end) {
1391 rrp[0]=rrp[1]=rrp[2]=0.0;
1395 analogtv_blast_imagerow(it, raw_rgb_start, raw_rgb_end,
1399 free(raw_rgb_start);
1402 /* poor attempt at visible retrace */
1403 for (i=0; i<15; i++) {
1404 int ytop=(int)((i*it->useheight/15 -
1405 it->useheight/2)*puheight) + it->useheight/2;
1406 int ybot=(int)(((i+1)*it->useheight/15 -
1407 it->useheight/2)*puheight) + it->useheight/2;
1408 int div=it->usewidth*3/2;
1410 for (x=0; x<it->usewidth; x++) {
1411 y = ytop + (ybot-ytop)*x / div;
1412 if (y<0 || y>=it->useheight) continue;
1413 XPutPixel(it->image, x, y, 0xffffff);
1418 if (it->need_clear) {
1419 XClearWindow(it->dpy, it->window);
1423 if (overall_top>0) {
1424 XClearArea(it->dpy, it->window,
1425 it->screen_xo, it->screen_yo,
1426 it->usewidth, overall_top, 0);
1428 if (it->useheight > overall_bot) {
1429 XClearArea(it->dpy, it->window,
1430 it->screen_xo, it->screen_yo+overall_bot,
1431 it->usewidth, it->useheight-overall_bot, 0);
1434 if (overall_bot > overall_top) {
1436 #ifdef HAVE_XSHM_EXTENSION
1437 XShmPutImage(it->dpy, it->window, it->gc, it->image,
1439 it->screen_xo, it->screen_yo+overall_top,
1440 it->usewidth, overall_bot - overall_top,
1444 XPutImage(it->dpy, it->window, it->gc, it->image,
1446 it->screen_xo, it->screen_yo+overall_top,
1447 it->usewidth, overall_bot - overall_top);
1456 gettimeofday(&tv,NULL);
1458 fps=1.0/((tv.tv_sec - it->last_display_time.tv_sec)
1459 + 0.000001*(tv.tv_usec - it->last_display_time.tv_usec));
1460 sprintf(buf, "FPS=%0.1f",fps);
1461 XDrawString(it->dpy, it->window, it->gc, 50, it->useheight*2/3,
1464 it->last_display_time=tv;
1472 analogtv_input_allocate()
1474 analogtv_input *ret=(analogtv_input *)calloc(1,sizeof(analogtv_input));
1480 This takes a screen image and encodes it as a video camera would,
1481 including all the bandlimiting and YIQ modulation.
1482 This isn't especially tuned for speed.
1486 analogtv_load_ximage(analogtv *it, analogtv_input *input, XImage *pic_im)
1493 XColor col1[ANALOGTV_PIC_LEN];
1494 XColor col2[ANALOGTV_PIC_LEN];
1495 int multiq[ANALOGTV_PIC_LEN+4];
1497 img_w=pic_im->width;
1498 img_h=pic_im->height;
1500 for (i=0; i<ANALOGTV_PIC_LEN+4; i++) {
1501 double phase=90.0-90.0*i;
1503 multiq[i]=(int)(-cos(3.1415926/180.0*(phase-303)) * 4096.0 * ampl);
1506 for (y=0; y<ANALOGTV_VISLINES; y++) {
1507 int picy1=(y*img_h)/ANALOGTV_VISLINES;
1508 int picy2=(y*img_h+ANALOGTV_VISLINES/2)/ANALOGTV_VISLINES;
1510 for (x=0; x<ANALOGTV_PIC_LEN; x++) {
1511 int picx=(x*img_w)/ANALOGTV_PIC_LEN;
1512 col1[x].pixel=XGetPixel(pic_im, picx, picy1);
1513 col2[x].pixel=XGetPixel(pic_im, picx, picy2);
1515 XQueryColors(it->dpy, it->colormap, col1, ANALOGTV_PIC_LEN);
1516 XQueryColors(it->dpy, it->colormap, col2, ANALOGTV_PIC_LEN);
1518 for (i=0; i<7; i++) fyx[i]=fyy[i]=0;
1519 for (i=0; i<4; i++) fix[i]=fiy[i]=fqx[i]=fqy[i]=0.0;
1521 for (x=0; x<ANALOGTV_PIC_LEN; x++) {
1523 int filty,filti,filtq;
1526 y=0.30 r + 0.59 g + 0.11 b
1527 i=0.60 r - 0.28 g - 0.32 b
1528 q=0.21 r - 0.52 g + 0.31 b
1529 The coefficients below are in .4 format */
1531 rawy=( 5*col1[x].red + 11*col1[x].green + 2*col1[x].blue +
1532 5*col2[x].red + 11*col2[x].green + 2*col2[x].blue)>>7;
1533 rawi=(10*col1[x].red - 4*col1[x].green - 5*col1[x].blue +
1534 10*col2[x].red - 4*col2[x].green - 5*col2[x].blue)>>7;
1535 rawq=( 3*col1[x].red - 8*col1[x].green + 5*col1[x].blue +
1536 3*col2[x].red - 8*col2[x].green + 5*col2[x].blue)>>7;
1538 /* Filter y at with a 4-pole low-pass Butterworth filter at 3.5 MHz
1539 with an extra zero at 3.5 MHz, from
1540 mkfilter -Bu -Lp -o 4 -a 2.1428571429e-01 0 -Z 2.5e-01 -l */
1542 fyx[0] = fyx[1]; fyx[1] = fyx[2]; fyx[2] = fyx[3];
1543 fyx[3] = fyx[4]; fyx[4] = fyx[5]; fyx[5] = fyx[6];
1544 fyx[6] = (rawy * 1897) >> 16;
1545 fyy[0] = fyy[1]; fyy[1] = fyy[2]; fyy[2] = fyy[3];
1546 fyy[3] = fyy[4]; fyy[4] = fyy[5]; fyy[5] = fyy[6];
1547 fyy[6] = (fyx[0]+fyx[6]) + 4*(fyx[1]+fyx[5]) + 7*(fyx[2]+fyx[4]) + 8*fyx[3]
1548 + ((-151*fyy[2] + 8115*fyy[3] - 38312*fyy[4] + 36586*fyy[5]) >> 16);
1551 /* Filter I at 1.5 MHz. 3 pole Butterworth from
1552 mkfilter -Bu -Lp -o 3 -a 1.0714285714e-01 0 */
1554 fix[0] = fix[1]; fix[1] = fix[2]; fix[2] = fix[3];
1555 fix[3] = (rawi * 1413) >> 16;
1556 fiy[0] = fiy[1]; fiy[1] = fiy[2]; fiy[2] = fiy[3];
1557 fiy[3] = (fix[0]+fix[3]) + 3*(fix[1]+fix[2])
1558 + ((16559*fiy[0] - 72008*fiy[1] + 109682*fiy[2]) >> 16);
1561 /* Filter Q at 0.5 MHz. 3 pole Butterworth from
1562 mkfilter -Bu -Lp -o 3 -a 3.5714285714e-02 0 -l */
1564 fqx[0] = fqx[1]; fqx[1] = fqx[2]; fqx[2] = fqx[3];
1565 fqx[3] = (rawq * 75) >> 16;
1566 fqy[0] = fqy[1]; fqy[1] = fqy[2]; fqy[2] = fqy[3];
1567 fqy[3] = (fqx[0]+fqx[3]) + 3 * (fqx[1]+fqx[2])
1568 + ((2612*fqy[0] - 9007*fqy[1] + 10453 * fqy[2]) >> 12);
1572 composite = filty + ((multiq[x] * filti + multiq[x+3] * filtq)>>12);
1573 composite = ((composite*100)>>14) + ANALOGTV_BLACK_LEVEL;
1574 if (composite>125) composite=125;
1575 if (composite<0) composite=0;
1576 input->signal[y+ANALOGTV_TOP][x+ANALOGTV_PIC_START] = composite;
1584 void analogtv_channel_noise(analogtv_input *it, analogtv_input *s2)
1587 int change=random()%ANALOGTV_V;
1588 unsigned int fastrnd=random();
1589 double hso=(int)(random()%1000)-500;
1590 int yofs=random()%ANALOGTV_V;
1593 for (y=change; y<ANALOGTV_V; y++) {
1594 int s2y=(y+yofs)%ANALOGTV_V;
1596 int noiselevel=60000 / (y-change+100);
1598 it->line_hsync[y] = s2->line_hsync[y] + (int)hso;
1600 for (x=0; x<ANALOGTV_H; x++) {
1602 filt+= (-filt/16) + (int)(fastrnd&0xfff)-0x800;
1603 noise=(filt*noiselevel)>>16;
1604 newsig=s2->signal[s2y][x] + noise;
1605 if (newsig>120) newsig=120;
1606 if (newsig<0) newsig=0;
1607 it->signal[y][x]=newsig;
1615 void analogtv_add_signal(analogtv *it, analogtv_reception *rec)
1617 analogtv_input *inp=rec->input;
1618 double *ps=it->rx_signal;
1619 double *pe=it->rx_signal + ANALOGTV_SIGNAL_LEN;
1621 char *ss=&inp->signal[0][0];
1622 char *se=&inp->signal[0][0] + ANALOGTV_SIGNAL_LEN;
1623 char *s=ss + ((unsigned)rec->ofs % ANALOGTV_SIGNAL_LEN);
1625 int ec=it->channel_change_cycles;
1626 double level=rec->level;
1627 double hfloss=rec->hfloss;
1628 unsigned int fastrnd=random();
1631 /* assert((se-ss)%4==0 && (se-s)%4==0); */
1633 /* duplicate the first line into the Nth line to ease wraparound computation */
1634 memcpy(inp->signal[ANALOGTV_V], inp->signal[0],
1635 ANALOGTV_H * sizeof(inp->signal[0][0]));
1637 for (i=0; i<8; i++) dp[i]=0.0;
1642 /* Do a big noisy transition. We can make the transition noise of
1643 high constant strength regardless of signal strength.
1645 There are two separate state machines. here, One is the noise
1646 process and the other is the
1648 We don't bother with the FIR filter here
1653 while (p!=pe && ec>0) {
1655 double sig0=(double)s[0];
1656 double noise = ((int)fastrnd-(int)0x7fffffff) * (50.0/(double)0x7fffffff);
1657 fastrnd = (fastrnd*1103515245+12345) & 0xffffffffu;
1659 p[0] += sig0 * level * (1.0 - noise_ampl) + noise * noise_ampl;
1661 noise_ampl *= 0.99995;
1672 double sig0,sig1,sig2,sig3,sigr;
1679 dp[0]=sig0+sig1+sig2+sig3;
1681 /* Get the video out signal, and add some ghosting, typical of RF
1682 monitor cables. This corresponds to a pretty long cable, but
1686 sigr=(dp[1]*rec->ghostfir[0] + dp[2]*rec->ghostfir[1] +
1687 dp[3]*rec->ghostfir[2] + dp[4]*rec->ghostfir[3]);
1688 dp[4]=dp[3]; dp[3]=dp[2]; dp[2]=dp[1]; dp[1]=dp[0];
1690 p[0] += (sig0+sigr + sig2*hfloss) * level;
1691 p[1] += (sig1+sigr + sig3*hfloss) * level;
1692 p[2] += (sig2+sigr + sig0*hfloss) * level;
1693 p[3] += (sig3+sigr + sig1*hfloss) * level;
1697 if (s>=se) s = ss + (s-se);
1700 it->rx_signal_level =
1701 sqrt(it->rx_signal_level * it->rx_signal_level +
1702 (level * level * (1.0 + 4.0*(rec->ghostfir[0] + rec->ghostfir[1] +
1703 rec->ghostfir[2] + rec->ghostfir[3]))));
1706 it->channel_change_cycles=0;
1712 if (it->hashnoise_times[lineno]) {
1713 int hnt=it->hashnoise_times[lineno] - input->line_hsync[lineno];
1715 if (hnt>=0 && hnt<ANALOGTV_PIC_LEN) {
1717 double cur=frand(150.0)-20.0;
1718 int len=random()%15+3;
1719 if (len > ANALOGTV_PIC_LEN-hnt) len=ANALOGTV_PIC_LEN-hnt;
1720 for (i=0; i<len; i++) {
1721 double sig=signal[hnt];
1724 cur += frand(5.0)-5.0;
1725 maxampl = maxampl*0.9;
1735 void analogtv_init_signal(analogtv *it, double noiselevel)
1737 double *ps=it->rx_signal;
1738 double *pe=it->rx_signal + ANALOGTV_SIGNAL_LEN;
1740 unsigned int fastrnd=random();
1741 double nm1=0.0,nm2=0.0;
1742 double noisemul = sqrt(noiselevel*150)/(double)0x7fffffff;
1746 nm1 = ((int)fastrnd-(int)0x7fffffff) * noisemul;
1748 fastrnd = (fastrnd*1103515245+12345) & 0xffffffffu;
1751 it->rx_signal_level = noiselevel;
1755 analogtv_reception_update(analogtv_reception *rec)
1759 if (rec->multipath > 0.0) {
1760 for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
1761 rec->ghostfir2[i] +=
1762 -(rec->ghostfir2[i]/16.0) + rec->multipath * (frand(0.02)-0.01);
1764 if (random()%20==0) {
1765 rec->ghostfir2[random()%(ANALOGTV_GHOSTFIR_LEN)]
1766 = rec->multipath * (frand(0.08)-0.04);
1768 for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
1769 rec->ghostfir[i] = 0.8*rec->ghostfir[i] + 0.2*rec->ghostfir2[i];
1773 rec->hfloss2 += -(rec->hfloss2/16.0) + rec->multipath * (frand(0.08)-0.04);
1774 rec->hfloss = 0.5*rec->hfloss + 0.5*rec->hfloss2;
1778 for (i=0; i<ANALOGTV_GHOSTFIR_LEN; i++) {
1779 rec->ghostfir[i] = (i>=ANALOGTV_GHOSTFIR_LEN/2) ? ((i&1) ? +0.04 : -0.08) /ANALOGTV_GHOSTFIR_LEN
1787 analogtv_make_font(Display *dpy, Window window, analogtv_font *f,
1788 int w, int h, char *fontname)
1795 XWindowAttributes xgwa;
1800 XGetWindowAttributes (dpy, window, &xgwa);
1804 font = XLoadQueryFont (dpy, fontname);
1806 fprintf(stderr, "analogtv: can't load font %s\n", fontname);
1810 text_pm=XCreatePixmap(dpy, window, 128*f->char_w, f->char_h, xgwa.depth);
1812 memset(&gcv, 0, sizeof(gcv));
1816 gc=XCreateGC(dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
1818 XSetForeground(dpy, gc, 0);
1819 XFillRectangle(dpy, text_pm, gc, 0, 0, 128*f->char_w, f->char_h);
1820 XSetForeground(dpy, gc, 1);
1822 for (i=0; i<128; i++) {
1824 int x=f->char_w*i+1;
1825 int y=f->char_h*8/10;
1826 XDrawString(dpy, text_pm, gc, x, y, &c, 1);
1828 f->text_im = XGetImage(dpy, text_pm, 0, 0, 128*f->char_w, f->char_h,
1831 XFreePixmap(dpy, text_pm);
1833 f->text_im = XCreateImage(dpy, xgwa.visual, xgwa.depth,
1835 128*f->char_w, f->char_h, 8, 0);
1836 f->text_im->data = (char *)calloc(f->text_im->height,
1837 f->text_im->bytes_per_line);
1845 analogtv_font_pixel(analogtv_font *f, int c, int x, int y)
1847 if (x<0 || x>=f->char_w) return 0;
1848 if (y<0 || y>=f->char_h) return 0;
1849 if (c<0 || c>=128) return 0;
1850 return XGetPixel(f->text_im, c*f->char_w + x, y) ? 1 : 0;
1854 analogtv_font_set_pixel(analogtv_font *f, int c, int x, int y, int value)
1856 if (x<0 || x>=f->char_w) return;
1857 if (y<0 || y>=f->char_h) return;
1858 if (c<0 || c>=128) return;
1860 XPutPixel(f->text_im, c*f->char_w + x, y, value);
1864 analogtv_font_set_char(analogtv_font *f, int c, char *s)
1868 if (c<0 || c>=128) return;
1870 for (y=0; y<f->char_h; y++) {
1871 for (x=0; x<f->char_w; x++) {
1873 value=(*s==' ') ? 0 : 1;
1874 analogtv_font_set_pixel(f, c, x, y, value);
1881 analogtv_lcp_to_ntsc(double luma, double chroma, double phase, int ntsc[4])
1884 for (i=0; i<4; i++) {
1885 double w=90.0*i + phase;
1886 double val=luma + chroma * (cos(3.1415926/180.0*w));
1887 if (val<0.0) val=0.0;
1888 if (val>127.0) val=127.0;
1894 analogtv_draw_solid(analogtv_input *input,
1895 int left, int right, int top, int bot,
1900 if (right-left<4) right=left+4;
1901 if (bot-top<1) bot=top+1;
1903 for (y=top; y<bot; y++) {
1904 for (x=left; x<right; x++) {
1905 input->signal[y][x] = ntsc[x&3];
1912 analogtv_draw_solid_rel_lcp(analogtv_input *input,
1913 double left, double right, double top, double bot,
1914 double luma, double chroma, double phase)
1918 int topi=(int)(ANALOGTV_TOP + ANALOGTV_VISLINES*top);
1919 int boti=(int)(ANALOGTV_TOP + ANALOGTV_VISLINES*bot);
1920 int lefti=(int)(ANALOGTV_VIS_START + ANALOGTV_VIS_LEN*left);
1921 int righti=(int)(ANALOGTV_VIS_START + ANALOGTV_VIS_LEN*right);
1923 analogtv_lcp_to_ntsc(luma, chroma, phase, ntsc);
1924 analogtv_draw_solid(input, lefti, righti, topi, boti, ntsc);
1929 analogtv_draw_char(analogtv_input *input, analogtv_font *f,
1930 int c, int x, int y, int ntsc[4])
1932 int yc,xc,ys,xs,pix;
1934 for (yc=0; yc<f->char_h; yc++) {
1935 for (ys=y + yc*f->y_mult; ys<y + (yc+1)*f->y_mult; ys++) {
1936 if (ys<0 || ys>=ANALOGTV_V) continue;
1938 for (xc=0; xc<f->char_w; xc++) {
1939 pix=analogtv_font_pixel(f, c, xc, yc);
1941 for (xs=x + xc*f->x_mult; xs<x + (xc+1)*f->x_mult; xs++) {
1942 if (xs<0 || xs>=ANALOGTV_H) continue;
1944 input->signal[ys][xs] = ntsc[xs&3];
1953 analogtv_draw_string(analogtv_input *input, analogtv_font *f,
1954 char *s, int x, int y, int ntsc[4])
1957 analogtv_draw_char(input, f, *s, x, y, ntsc);
1964 analogtv_draw_string_centered(analogtv_input *input, analogtv_font *f,
1965 char *s, int x, int y, int ntsc[4])
1967 int width=strlen(s) * f->char_w * 4;
1970 analogtv_draw_string(input, f, s, x, y, ntsc);
1974 static const char hextonib[128] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1975 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1976 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1977 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
1978 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
1979 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1980 0, 10,11,12,13,14,15,0, 0, 0, 0, 0, 0, 0, 0, 0,
1981 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
1984 Much of this function was adapted from logo.c
1987 analogtv_draw_xpm(analogtv *tv, analogtv_input *input,
1988 const char * const *xpm, int left, int top)
1993 int ncolors, nbytes;
1996 int r; int g; int b;
2000 if (4 != sscanf ((const char *) *xpm,
2002 &xpmw, &xpmh, &ncolors, &nbytes, &dummyc))
2004 if (ncolors < 1 || ncolors > 255)
2006 if (nbytes != 1) /* a serious limitation */
2010 for (i = 0; i < ncolors; i++) {
2011 const char *line = *xpm;
2012 int colori = ((unsigned char)*line++)&0xff;
2017 while (*line == ' ' || *line == '\t')
2020 if (which != 'c' && which != 'm')
2022 while (*line == ' ' || *line == '\t')
2024 if (!strncasecmp(line, "None", 4))
2033 r = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
2035 g = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
2037 b = (hextonib[(int) line[0]] << 4) | hextonib[(int) line[1]];
2052 for (y=0; y<xpmh; y++) {
2053 const char *line = *xpm++;
2055 if (tvy<ANALOGTV_TOP || tvy>=ANALOGTV_BOT) continue;
2057 for (x=0; x<xpmw; x++) {
2058 int cbyte=((unsigned char)line[x])&0xff;
2061 if (tvx<ANALOGTV_PIC_START || tvx+4>ANALOGTV_PIC_END) continue;
2063 rawy=( 5*cmap[cbyte].r + 11*cmap[cbyte].g + 2*cmap[cbyte].b) / 64;
2064 rawi=(10*cmap[cbyte].r - 4*cmap[cbyte].g - 5*cmap[cbyte].b) / 64;
2065 rawq=( 3*cmap[cbyte].r - 8*cmap[cbyte].g + 5*cmap[cbyte].b) / 64;
2072 for (i=0; i<4; i++) {
2073 if (ntsc[i]>ANALOGTV_WHITE_LEVEL) ntsc[i]=ANALOGTV_WHITE_LEVEL;
2074 if (ntsc[i]<ANALOGTV_BLACK_LEVEL) ntsc[i]=ANALOGTV_BLACK_LEVEL;
2077 input->signal[tvy][tvx+0]= ntsc[(tvx+0)&3];
2078 input->signal[tvy][tvx+1]= ntsc[(tvx+1)&3];
2079 input->signal[tvy][tvx+2]= ntsc[(tvx+2)&3];
2080 input->signal[tvy][tvx+3]= ntsc[(tvx+3)&3];
2085 extern XtAppContext app;
2088 analogtv_handle_events (analogtv *it)
2090 XSync(it->dpy, False);
2091 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
2092 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
2094 while (XPending (it->dpy))
2097 XNextEvent (it->dpy, &event);
2098 switch (event.xany.type)
2107 XLookupString (&event.xkey, &c, 1, &keysym, 0);
2108 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
2113 /* I don't seem to get an event when clicking the "full
2114 screen" window manager icon, at least when using
2115 metacity. Thus, it doesn't change the video size. Is this
2116 some separate WM_* message I have to deal with?
2118 case ConfigureNotify:
2119 if (event.xconfigure.width != it->xgwa.width ||
2120 event.xconfigure.height != it->xgwa.height)
2121 analogtv_reconfigure(it);
2125 case GraphicsExpose:
2132 if (it->event_handler) {
2133 (*it->event_handler) (it->dpy, &event);