X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Fanalogtv.c;h=8d64d50370558ccfe04af87c8d4b4e7fa70b7010;hp=1ff995d2ccef12ac4f2f52cb5abc860cce45418b;hb=8afc01a67be4fbf3f1cc0fce9adf01b5289a21c6;hpb=3f1091236d800c43a3124c44c7da54e53f205b13 diff --git a/hacks/analogtv.c b/hacks/analogtv.c index 1ff995d2..8d64d503 100644 --- a/hacks/analogtv.c +++ b/hacks/analogtv.c @@ -51,6 +51,14 @@ Trevor Blackwell */ +/* + 2014-04-20, Dave Odell : + API change: Folded analogtv_init_signal and *_add_signal into *_draw(). + Added SMP support. + Replaced doubles with floats, including constants and transcendental functions. + Fixed a bug or two. +*/ + #ifdef HAVE_COCOA # include "jwxyz.h" #else /* !HAVE_COCOA */ @@ -59,11 +67,13 @@ #endif #include +#include #include "utils.h" #include "resources.h" #include "analogtv.h" #include "yarandom.h" #include "grabscreen.h" +#include "visual.h" /* #define DEBUG 1 */ @@ -93,20 +103,22 @@ do { \ #endif -#define FASTRND (fastrnd = fastrnd*1103515245+12345) +#define FASTRND_A 1103515245 +#define FASTRND_C 12345 +#define FASTRND (fastrnd = fastrnd*FASTRND_A+FASTRND_C) -static void analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal, - int start, int end); +static void analogtv_ntsc_to_yiq(const analogtv *it, int lineno, const float *signal, + int start, int end, struct analogtv_yiq_s *it_yiq); -static double puramp(analogtv *it, double tc, double start, double over) +static float puramp(const analogtv *it, float tc, float start, float over) { - double pt=it->powerup-start; - double ret; - if (pt<0.0) return 0.0; - if (pt>900.0 || pt/tc>8.0) return 1.0; + float pt=it->powerup-start; + float ret; + if (pt<0.0f) return 0.0f; + if (pt>900.0f || pt/tc>8.0f) return 1.0f; - ret=(1.0-exp(-pt/tc))*over; - if (ret>1.0) return 1.0; + ret=(1.0f-expf(-pt/tc))*over; + if (ret>1.0f) return 1.0f; return ret*ret; } @@ -249,6 +261,8 @@ analogtv_free_image(analogtv *it) destroy_xshm_image(it->dpy, it->image, &it->shm_info); #endif } else { + thread_free(it->image->data); + it->image->data = NULL; XDestroyImage(it->image); } it->image=NULL; @@ -258,19 +272,40 @@ analogtv_free_image(analogtv *it) static void analogtv_alloc_image(analogtv *it) { + /* On failure, it->image is NULL. */ + + unsigned bits_per_pixel = get_bits_per_pixel(it->dpy, it->xgwa.depth); + unsigned align = thread_memory_alignment(it->dpy) * 8 - 1; + /* Width is in bits. */ + unsigned width = (it->usewidth * bits_per_pixel + align) & ~align; + if (it->use_shm) { #ifdef HAVE_XSHM_EXTENSION it->image=create_xshm_image(it->dpy, it->xgwa.visual, it->xgwa.depth, ZPixmap, 0, - &it->shm_info, it->usewidth, it->useheight); + &it->shm_info, + width / bits_per_pixel, it->useheight); #endif if (!it->image) it->use_shm=0; } if (!it->image) { it->image = XCreateImage(it->dpy, it->xgwa.visual, it->xgwa.depth, ZPixmap, 0, 0, - it->usewidth, it->useheight, 8, 0); - it->image->data = (char *)malloc(it->image->height * it->image->bytes_per_line); + it->usewidth, it->useheight, 8, width / 8); + if (it->image) { + if(thread_malloc((void **)&it->image->data, it->dpy, + it->image->height * it->image->bytes_per_line)) { + it->image->data = NULL; + XDestroyImage(it->image); + it->image = NULL; + } + } + } + + if (it->image) { + memset (it->image->data, 0, it->image->height * it->image->bytes_per_line); + } else { + /* Not enough memory. Maybe try a smaller window. */ + fprintf(stderr, "analogtv: %s\n", strerror(ENOMEM)); } - memset (it->image->data, 0, it->image->height * it->image->bytes_per_line); } @@ -306,8 +341,8 @@ analogtv_configure(analogtv *it) #ifdef USE_IPHONE /* Fill the whole iPhone screen, even though that distorts the image. */ - min_ratio = 320.0 / 480.0 * (1 - percent); - max_ratio = 480.0 / 320.0 * (1 + percent); + min_ratio = 640.0 / 1136.0 * (1 - percent); + max_ratio = 1136.0 / 640.0 * (1 + percent); #endif if (wlim < 266 || hlim < 200) @@ -384,20 +419,86 @@ analogtv_reconfigure(analogtv *it) analogtv_configure(it); } +/* Can be any power-of-two <= 32. 16 a slightly better choice for 2-3 threads. */ +#define ANALOGTV_SUBTOTAL_LEN 32 + +typedef struct analogtv_thread_s +{ + analogtv *it; + unsigned thread_id; + size_t signal_start, signal_end; +} analogtv_thread; + +#define SIGNAL_OFFSET(thread_id) \ + ((ANALOGTV_SIGNAL_LEN * (thread_id) / threads->count) & align) + +static int analogtv_thread_create(void *thread_raw, struct threadpool *threads, + unsigned thread_id) +{ + analogtv_thread *thread = (analogtv_thread *)thread_raw; + unsigned align; + + thread->it = GET_PARENT_OBJ(analogtv, threads, threads); + thread->thread_id = thread_id; + + align = thread_memory_alignment(thread->it->dpy) / + sizeof(thread->it->signal_subtotals[0]); + if (!align) + align = 1; + align = ~(align * ANALOGTV_SUBTOTAL_LEN - 1); + + thread->signal_start = SIGNAL_OFFSET(thread_id); + thread->signal_end = thread_id + 1 == threads->count ? + ANALOGTV_SIGNAL_LEN : + SIGNAL_OFFSET(thread_id + 1); + + return 0; +} + +static void analogtv_thread_destroy(void *thread_raw) +{ +} + analogtv * analogtv_allocate(Display *dpy, Window window) { + static const struct threadpool_class cls = { + sizeof(analogtv_thread), + analogtv_thread_create, + analogtv_thread_destroy + }; + XGCValues gcv; analogtv *it=NULL; int i; + const size_t rx_signal_len = ANALOGTV_SIGNAL_LEN + 2*ANALOGTV_H; analogtv_init(); it=(analogtv *)calloc(1,sizeof(analogtv)); if (!it) return 0; + it->threads.count=0; + it->rx_signal=NULL; + it->signal_subtotals=NULL; + it->dpy=dpy; it->window=window; + if (thread_malloc((void **)&it->rx_signal, dpy, + sizeof(it->rx_signal[0]) * rx_signal_len)) + goto fail; + + assert(!(ANALOGTV_SIGNAL_LEN % ANALOGTV_SUBTOTAL_LEN)); + if (thread_malloc((void **)&it->signal_subtotals, dpy, + sizeof(it->signal_subtotals[0]) * + (rx_signal_len / ANALOGTV_SUBTOTAL_LEN))) + goto fail; + + if (threadpool_create(&it->threads, &cls, dpy, hardware_concurrency(dpy))) + goto fail; + + assert(it->threads.count); + it->shrinkpulse=-1; it->n_colors=0; @@ -479,7 +580,13 @@ analogtv_allocate(Display *dpy, Window window) return it; fail: - if (it) free(it); + if (it) { + if(it->threads.count) + threadpool_destroy(&it->threads); + thread_free(it->signal_subtotals); + thread_free(it->rx_signal); + free(it); + } return NULL; } @@ -492,6 +599,8 @@ analogtv_release(analogtv *it) destroy_xshm_image(it->dpy, it->image, &it->shm_info); #endif } else { + thread_free(it->image->data); + it->image->data = NULL; XDestroyImage(it->image); } it->image=NULL; @@ -500,6 +609,9 @@ analogtv_release(analogtv *it) it->gc=NULL; if (it->n_colors) XFreeColors(it->dpy, it->colormap, it->colors, it->n_colors, 0L); it->n_colors=0; + threadpool_destroy(&it->threads); + thread_free(it->rx_signal); + thread_free(it->signal_subtotals); free(it); } @@ -656,19 +768,19 @@ analogtv_line_signature(analogtv_input *input, int lineno) */ static void -analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal, - int start, int end) +analogtv_ntsc_to_yiq(const analogtv *it, int lineno, const float *signal, + int start, int end, struct analogtv_yiq_s *it_yiq) { enum {MAXDELAY=32}; int i; - double *sp; + const float *sp; int phasecorr=(signal-it->rx_signal)&3; struct analogtv_yiq_s *yiq; int colormode; - double agclevel=it->agclevel; - double brightadd=it->brightness_control*100.0 - ANALOGTV_BLACK_LEVEL; - double delay[MAXDELAY+ANALOGTV_PIC_LEN], *dp; - double multiq2[4]; + float agclevel=it->agclevel; + float brightadd=it->brightness_control*100.0 - ANALOGTV_BLACK_LEVEL; + float delay[MAXDELAY+ANALOGTV_PIC_LEN], *dp; + float multiq2[4]; { @@ -703,14 +815,14 @@ analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal, #endif dp=delay+ANALOGTV_PIC_LEN-MAXDELAY; - for (i=0; i<5; i++) dp[i]=0.0; + for (i=0; i<5; i++) dp[i]=0.0f; assert(start>=0); assert(end < ANALOGTV_PIC_LEN+10); dp=delay+ANALOGTV_PIC_LEN-MAXDELAY; for (i=0; i<24; i++) dp[i]=0.0; - for (i=start, yiq=it->yiq+start, sp=signal+start; + for (i=start, yiq=it_yiq+start, sp=signal+start; iy = dp[8] + brightadd; } @@ -744,10 +856,10 @@ analogtv_ntsc_to_yiq(analogtv *it, int lineno, double *signal, dp=delay+ANALOGTV_PIC_LEN-MAXDELAY; for (i=0; i<27; i++) dp[i]=0.0; - for (i=start, yiq=it->yiq+start, sp=signal+start; + for (i=start, yiq=it_yiq+start, sp=signal+start; ii=dp[8] = (dp[5] + dp[0] - +3.0*(dp[4] + dp[1]) - +4.0*(dp[3] + dp[2]) - -0.3333333333 * dp[10]); + +3.0f*(dp[4] + dp[1]) + +4.0f*(dp[3] + dp[2]) + -0.3333333333f * dp[10]); - dp[16] = sig*multiq2[(i+3)&3] * 0.0833333333333; + dp[16] = sig*multiq2[(i+3)&3] * 0.0833333333333f; yiq->q=dp[24] = (dp[16+5] + dp[16+0] - +3.0*(dp[16+4] + dp[16+1]) - +4.0*(dp[16+3] + dp[16+2]) - -0.3333333333 * dp[24+2]); + +3.0f*(dp[16+4] + dp[16+1]) + +4.0f*(dp[16+3] + dp[16+2]) + -0.3333333333f * dp[24+2]); } } else { - for (i=start, yiq=it->yiq+start; ii = yiq->q = 0.0; + for (i=start, yiq=it_yiq+start; ii = yiq->q = 0.0f; } } } @@ -902,37 +1014,37 @@ analogtv_sync(analogtv *it) int cur_vsync=it->cur_vsync; int lineno = 0; int i,j; - double osc,filt; - double *sp; - double cbfc=1.0/128.0; + float osc,filt; + float *sp; + float cbfc=1.0f/128.0f; /* sp = it->rx_signal + lineno*ANALOGTV_H + cur_hsync;*/ for (i=-32; i<32; i++) { lineno = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V; sp = it->rx_signal + lineno*ANALOGTV_H; - filt=0.0; + filt=0.0f; for (j=0; jagclevel; - osc = (double)(ANALOGTV_V+i)/(double)ANALOGTV_V; + osc = (float)(ANALOGTV_V+i)/(float)ANALOGTV_V; - if (osc >= 1.05+0.0002 * filt) break; + if (osc >= 1.05f+0.0002f * filt) break; } cur_vsync = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V; for (lineno=0; lineno5 && linenorx_signal + ((lineno + cur_vsync + ANALOGTV_V)%ANALOGTV_V - )*ANALOGTV_H + cur_hsync; + unsigned lineno2 = (lineno + cur_vsync + ANALOGTV_V)%ANALOGTV_V; + if (!lineno2) lineno2 = ANALOGTV_V; + sp = it->rx_signal + lineno2*ANALOGTV_H + cur_hsync; for (i=-8; i<8; i++) { - osc = (double)(ANALOGTV_H+i)/(double)ANALOGTV_H; + osc = (float)(ANALOGTV_H+i)/(float)ANALOGTV_H; filt=(sp[i-3]+sp[i-2]+sp[i-1]+sp[i]) * it->agclevel; - if (osc >= 1.005 + 0.0001*filt) break; + if (osc >= 1.005f + 0.0001f*filt) break; } cur_hsync = (cur_hsync + i + ANALOGTV_H) % ANALOGTV_H; } @@ -949,19 +1061,19 @@ analogtv_sync(analogtv *it) if (lineno>15) { sp = it->rx_signal + lineno*ANALOGTV_H + (cur_hsync&~3); for (i=ANALOGTV_CB_START+8; icb_phase[i&3] = it->cb_phase[i&3]*(1.0-cbfc) + + it->cb_phase[i&3] = it->cb_phase[i&3]*(1.0f-cbfc) + sp[i]*it->agclevel*cbfc; } } { - double tot=0.1; - double cbgain; + float tot=0.1f; + float cbgain; for (i=0; i<4; i++) { tot += it->cb_phase[i]*it->cb_phase[i]; } - cbgain = 32.0/sqrt(tot); + cbgain = 32.0f/sqrtf(tot); for (i=0; i<4; i++) { it->line_cb_phase[lineno][i]=it->cb_phase[i]*cbgain; @@ -983,14 +1095,14 @@ analogtv_sync(analogtv *it) } static double -analogtv_levelmult(analogtv *it, int level) +analogtv_levelmult(const analogtv *it, int level) { static const double levelfac[3]={-7.5, 5.5, 24.5}; return (40.0 + levelfac[level]*puramp(it, 3.0, 6.0, 1.0))/256.0; } static int -analogtv_level(analogtv *it, int y, int ytop, int ybot) +analogtv_level(const analogtv *it, int y, int ytop, int ybot) { int level; if (ybot-ytop>=7) { @@ -1052,8 +1164,209 @@ analogtv_setup_levels(analogtv *it, double avgheight) } } +static void rnd_combine(unsigned *a0, unsigned *c0, unsigned a1, unsigned c1) +{ + *a0 = (*a0 * a1) & 0xffffffffu; + *c0 = (c1 + a1 * *c0) & 0xffffffffu; +} + +static void rnd_seek_ac(unsigned *a, unsigned *c, unsigned dist) +{ + unsigned int a1 = *a, c1 = *c; + *a = 1, *c = 0; + + while(dist) + { + if(dist & 1) + rnd_combine(a, c, a1, c1); + dist >>= 1; + rnd_combine(&a1, &c1, a1, c1); + } +} + +static unsigned int rnd_seek(unsigned a, unsigned c, unsigned rnd, unsigned dist) +{ + rnd_seek_ac(&a, &c, dist); + return a * rnd + c; +} + +static void analogtv_init_signal(const analogtv *it, double noiselevel, unsigned start, unsigned end) +{ + float *ps=it->rx_signal + start; + float *pe=it->rx_signal + end; + float *p=ps; + unsigned int fastrnd=rnd_seek(FASTRND_A, FASTRND_C, it->random0, start); + float nm1,nm2; + float noisemul = sqrt(noiselevel*150)/(float)0x7fffffff; + + nm1 = ((int)fastrnd-(int)0x7fffffff) * noisemul; + while (p != pe) { + nm2=nm1; + fastrnd = (fastrnd*FASTRND_A+FASTRND_C) & 0xffffffffu; + nm1 = ((int)fastrnd-(int)0x7fffffff) * noisemul; + *p++ = nm1*nm2; + } +} + +static void analogtv_add_signal(const analogtv *it, const analogtv_reception *rec, unsigned start, unsigned end, int ec) +{ + analogtv_input *inp=rec->input; + float *ps=it->rx_signal + start; + float *pe=it->rx_signal + end; + float *p=ps; + signed char *ss=&inp->signal[0][0]; + signed char *se=&inp->signal[0][0] + ANALOGTV_SIGNAL_LEN; + signed char *s=ss + ((start + (unsigned)rec->ofs) % ANALOGTV_SIGNAL_LEN); + signed char *s2; + int i; + float level=rec->level; + float hfloss=rec->hfloss; + unsigned int fastrnd=rnd_seek(FASTRND_A, FASTRND_C, it->random1, start); + float dp[5]; + + const float noise_decay = 0.99995f; + float noise_ampl = 1.3f * powf(noise_decay, start); + + if (ec > end) + ec = end; + + /* assert((se-ss)%4==0 && (se-s)%4==0); */ + + for (i = start; i < ec; i++) { /* Sometimes start > ec. */ + + /* Do a big noisy transition. We can make the transition noise of + high constant strength regardless of signal strength. + + There are two separate state machines. here, One is the noise + process and the other is the + + We don't bother with the FIR filter here + */ + + float sig0=(float)s[0]; + float noise = ((int)fastrnd-(int)0x7fffffff) * (50.0f/(float)0x7fffffff); + fastrnd = (fastrnd*FASTRND_A+FASTRND_C) & 0xffffffffu; + + p[0] += sig0 * level * (1.0f - noise_ampl) + noise * noise_ampl; + + noise_ampl *= noise_decay; + + p++; + s++; + if (s>=se) s=ss; + } + + dp[0]=0.0; + s2 = s; + for (i=1; i<5; i++) { + s2 -= 4; + if (s2 < ss) + s2 += ANALOGTV_SIGNAL_LEN; + dp[i] = (float)((int)s2[0]+(int)s2[1]+(int)s2[2]+(int)s2[3]); + } + + assert(p <= pe); + assert(!((pe - p) % 4)); + while (p != pe) { + float sig0,sig1,sig2,sig3,sigr; + + sig0=(float)s[0]; + sig1=(float)s[1]; + sig2=(float)s[2]; + sig3=(float)s[3]; + + dp[0]=sig0+sig1+sig2+sig3; + + /* Get the video out signal, and add some ghosting, typical of RF + monitor cables. This corresponds to a pretty long cable, but + looks right to me. + */ + + sigr=(dp[1]*rec->ghostfir[0] + dp[2]*rec->ghostfir[1] + + dp[3]*rec->ghostfir[2] + dp[4]*rec->ghostfir[3]); + dp[4]=dp[3]; dp[3]=dp[2]; dp[2]=dp[1]; dp[1]=dp[0]; + + p[0] += (sig0+sigr + sig2*hfloss) * level; + p[1] += (sig1+sigr + sig3*hfloss) * level; + p[2] += (sig2+sigr + sig0*hfloss) * level; + p[3] += (sig3+sigr + sig1*hfloss) * level; + + p += 4; + s += 4; + if (s>=se) s = ss + (s-se); + } + + assert(p == pe); +} + +static void analogtv_thread_add_signals(void *thread_raw) +{ + const analogtv_thread *thread = (analogtv_thread *)thread_raw; + const analogtv *it = thread->it; + unsigned i, j; + unsigned subtotal_end; + + unsigned start = thread->signal_start; + while(start != thread->signal_end) + { + float *p; + + /* Work on 8 KB blocks; these should fit in L1. */ + /* (Though it doesn't seem to help much on my system.) */ + unsigned end = start + 2048; + if(end > thread->signal_end) + end = thread->signal_end; + + analogtv_init_signal (it, it->noiselevel, start, end); + + for (i = 0; i != it->rec_count; ++i) { + analogtv_add_signal (it, it->recs[i], start, end, + !i ? it->channel_change_cycles : 0); + } + + assert (!(start % ANALOGTV_SUBTOTAL_LEN)); + assert (!(end % ANALOGTV_SUBTOTAL_LEN)); + + p = it->rx_signal + start; + subtotal_end = end / ANALOGTV_SUBTOTAL_LEN; + for (i = start / ANALOGTV_SUBTOTAL_LEN; i != subtotal_end; ++i) { + float sum = p[0]; + for (j = 1; j != ANALOGTV_SUBTOTAL_LEN; ++j) + sum += p[j]; + it->signal_subtotals[i] = sum; + p += ANALOGTV_SUBTOTAL_LEN; + } + + start = end; + } +} + +static int analogtv_get_line(const analogtv *it, int lineno, int *slineno, + int *ytop, int *ybot, unsigned *signal_offset) +{ + *slineno=lineno-ANALOGTV_TOP; + *ytop=(int)((*slineno*it->useheight/ANALOGTV_VISLINES - + it->useheight/2)*it->puheight) + it->useheight/2; + *ybot=(int)(((*slineno+1)*it->useheight/ANALOGTV_VISLINES - + it->useheight/2)*it->puheight) + it->useheight/2; +#if 0 + int linesig=analogtv_line_signature(input,lineno) + + it->hashnoise_times[lineno]; +#endif + *signal_offset = ((lineno+it->cur_vsync+ANALOGTV_V) % ANALOGTV_V) * ANALOGTV_H + + it->line_hsync[lineno]; + + if (*ytop==*ybot) return 0; + if (*ybot<0 || *ytop>it->useheight) return 0; + if (*ytop<0) *ytop=0; + if (*ybot>it->useheight) *ybot=it->useheight; + + if (*ybot > *ytop+ANALOGTV_MAX_LINEHEIGHT) *ybot=*ytop+ANALOGTV_MAX_LINEHEIGHT; + return 1; +} + static void -analogtv_blast_imagerow(analogtv *it, +analogtv_blast_imagerow(const analogtv *it, float *rgbf, float *rgbf_end, int ytop, int ybot) { @@ -1061,14 +1374,16 @@ analogtv_blast_imagerow(analogtv *it, float *rpf; char *level_copyfrom[3]; int xrepl=it->xrepl; + unsigned lineheight = ybot - ytop; + if (lineheight > ANALOGTV_MAX_LINEHEIGHT) lineheight = ANALOGTV_MAX_LINEHEIGHT; for (i=0; i<3; i++) level_copyfrom[i]=NULL; for (y=ytop; yleveltable[ybot-ytop][y-ytop].index; - double levelmult=it->leveltable[ybot-ytop][y-ytop].value; - char *rowdata; + char *rowdata=it->image->data + y*it->image->bytes_per_line; + unsigned line = y-ytop; - rowdata=it->image->data + y*it->image->bytes_per_line; + int level=it->leveltable[lineheight][line].index; + float levelmult=it->leveltable[lineheight][line].value; /* Fast special cases to avoid the slow XPutPixel. Ugh. It goes to show why standard graphics sw has to be fast, or else people will have to @@ -1118,7 +1433,7 @@ analogtv_blast_imagerow(analogtv *it, float_extraction_works && it->image->byte_order==localbyteorder) { unsigned short *pixelptr=(unsigned short *)rowdata; - double r2,g2,b2; + float r2,g2,b2; float_extract_t r1,g1,b1; unsigned short pix; @@ -1180,176 +1495,78 @@ analogtv_blast_imagerow(analogtv *it, } } -void -analogtv_draw(analogtv *it) +static void analogtv_thread_draw_lines(void *thread_raw) { - int i,j,x,y,lineno; - int scanstart_i,scanend_i,squishright_i,squishdiv,pixrate; - float *rgb_start, *rgb_end; - double pixbright; - int pixmultinc; - int /*bigloadchange,*/drawcount; - double baseload; - double puheight; - int overall_top, overall_bot; + const analogtv_thread *thread = (analogtv_thread *)thread_raw; + const analogtv *it = thread->it; - float *raw_rgb_start=(float *)calloc(it->subwidth*3, sizeof(float)); - float *raw_rgb_end=raw_rgb_start+3*it->subwidth; - float *rrp; + int lineno; - if (! raw_rgb_start) return; - analogtv_setup_frame(it); - analogtv_set_demod(it); + float *raw_rgb_start; + float *raw_rgb_end; + raw_rgb_start=(float *)calloc(it->subwidth*3, sizeof(float)); - /* rx_signal has an extra 2 lines at the end, where we copy the - first 2 lines so we can index into it while only worrying about - wraparound on a per-line level */ - memcpy(&it->rx_signal[ANALOGTV_SIGNAL_LEN], - &it->rx_signal[0], - 2*ANALOGTV_H*sizeof(it->rx_signal[0])); - - analogtv_sync(it); - - baseload=0.5; - /* if (it->hashnoise_on) baseload=0.5; */ - - /*bigloadchange=1;*/ - drawcount=0; - it->crtload[ANALOGTV_TOP-1]=baseload; - puheight = puramp(it, 2.0, 1.0, 1.3) * it->height_control * - (1.125 - 0.125*puramp(it, 2.0, 2.0, 1.1)); + if (! raw_rgb_start) return; - analogtv_setup_levels(it, puheight * (double)it->useheight/(double)ANALOGTV_VISLINES); + raw_rgb_end=raw_rgb_start+3*it->subwidth; - overall_top=it->useheight; - overall_bot=0; + for (lineno=ANALOGTV_TOP + thread->thread_id; + linenothreads.count) { + int i,j,x,y; - for (lineno=ANALOGTV_TOP; linenouseheight/ANALOGTV_VISLINES - - it->useheight/2)*puheight) + it->useheight/2; - int ybot=(int)(((slineno+1)*it->useheight/ANALOGTV_VISLINES - - it->useheight/2)*puheight) + it->useheight/2; -#if 0 - int linesig=analogtv_line_signature(input,lineno) - + it->hashnoise_times[lineno]; -#endif - double *signal=(it->rx_signal + ((lineno + it->cur_vsync + - ANALOGTV_V)%ANALOGTV_V) * ANALOGTV_H + - it->line_hsync[lineno]); + int slineno, ytop, ybot; + unsigned signal_offset; - if (ytop==ybot) continue; - if (ybot<0 || ytop>it->useheight) continue; - if (ytop<0) ytop=0; - if (ybot>it->useheight) ybot=it->useheight; + const float *signal; - if (ybot > ytop+ANALOGTV_MAX_LINEHEIGHT) ybot=ytop+ANALOGTV_MAX_LINEHEIGHT; + int scanstart_i,scanend_i,squishright_i,squishdiv,pixrate; + float *rgb_start, *rgb_end; + float pixbright; + int pixmultinc; - if (ytop < overall_top) overall_top=ytop; - if (ybot > overall_bot) overall_bot=ybot; + float *rrp; - if (lineno==it->shrinkpulse) { - baseload += 0.4; - /*bigloadchange=1;*/ - it->shrinkpulse=-1; - } + struct analogtv_yiq_s yiq[ANALOGTV_PIC_LEN+10]; -#if 0 - if (it->hashnoise_rpm>0.0 && - !(bigloadchange || - it->redraw_all || - (slineno<20 && it->flutter_horiz_desync) || - it->gaussiannoise_level>30 || - ((it->gaussiannoise_level>2.0 || - it->multipath) && random()%4) || - linesig != it->onscreen_signature[lineno])) { + if (! analogtv_get_line(it, lineno, &slineno, &ytop, &ybot, + &signal_offset)) continue; - } - it->onscreen_signature[lineno] = linesig; -#endif - drawcount++; - - /* - Interpolate the 600-dotclock line into however many horizontal - screen pixels we're using, and convert to RGB. - We add some 'bloom', variations in the horizontal scan width with - the amount of brightness, extremely common on period TV sets. They - had a single oscillator which generated both the horizontal scan and - (during the horizontal retrace interval) the high voltage for the - electron beam. More brightness meant more load on the oscillator, - which caused an decrease in horizontal deflection. Look for - (bloomthisrow). - - Also, the A2 did a bad job of generating horizontal sync pulses - during the vertical blanking interval. This, and the fact that the - horizontal frequency was a bit off meant that TVs usually went a bit - out of sync during the vertical retrace, and the top of the screen - would be bent a bit to the left or right. Look for (shiftthisrow). - - We also add a teeny bit of left overscan, just enough to be - annoying, but you can still read the left column of text. - - We also simulate compression & brightening on the right side of the - screen. Most TVs do this, but you don't notice because they overscan - so it's off the right edge of the CRT. But the A2 video system used - so much of the horizontal scan line that you had to crank the - horizontal width down in order to not lose the right few characters, - and you'd see the compression on the right edge. Associated with - compression is brightening; since the electron beam was scanning - slower, the same drive signal hit the phosphor harder. Look for - (squishright_i) and (squishdiv). - */ + signal = it->rx_signal + signal_offset; { - int totsignal=0; - double ncl/*,diff*/; - for (i=0; iagclevel; - ncl = 0.95 * it->crtload[lineno-1] + - 0.05*(baseload + - (totsignal-30000)/100000.0 + - (slineno>184 ? (slineno-184)*(lineno-184)*0.001 * it->squeezebottom - : 0.0)); - /*diff=ncl - it->crtload[lineno];*/ - /*bigloadchange = (diff>0.01 || diff<-0.01);*/ - it->crtload[lineno]=ncl; - } - - { - double bloomthisrow,shiftthisrow; - double viswidth,middle; - double scanwidth; + float bloomthisrow,shiftthisrow; + float viswidth,middle; + float scanwidth; int scw,scl,scr; - bloomthisrow = -10.0 * it->crtload[lineno]; - if (bloomthisrow<-10.0) bloomthisrow=-10.0; - if (bloomthisrow>2.0) bloomthisrow=2.0; + bloomthisrow = -10.0f * it->crtload[lineno]; + if (bloomthisrow<-10.0f) bloomthisrow=-10.0f; + if (bloomthisrow>2.0f) bloomthisrow=2.0f; if (slineno<16) { - shiftthisrow=it->horiz_desync * (exp(-0.17*slineno) * - (0.7+cos(slineno*0.6))); + shiftthisrow=it->horiz_desync * (expf(-0.17f*slineno) * + (0.7f+cosf(slineno*0.6f))); } else { - shiftthisrow=0.0; + shiftthisrow=0.0f; } - viswidth=ANALOGTV_PIC_LEN * 0.79 - 5.0*bloomthisrow; + viswidth=ANALOGTV_PIC_LEN * 0.79f - 5.0f*bloomthisrow; middle=ANALOGTV_PIC_LEN/2 - shiftthisrow; - scanwidth=it->width_control * puramp(it, 0.5, 0.3, 1.0); + scanwidth=it->width_control * puramp(it, 0.5f, 0.3f, 1.0f); scw=it->subwidth*scanwidth; if (scw>it->subwidth) scw=it->usewidth; scl=it->subwidth/2 - scw/2; scr=it->subwidth/2 + scw/2; - pixrate=(int)((viswidth*65536.0*1.0)/it->subwidth)/scanwidth; - scanstart_i=(int)((middle-viswidth*0.5)*65536.0); + pixrate=(int)((viswidth*65536.0f*1.0f)/it->subwidth)/scanwidth; + scanstart_i=(int)((middle-viswidth*0.5f)*65536.0f); scanend_i=(ANALOGTV_PIC_LEN-1)*65536; - squishright_i=(int)((middle+viswidth*(0.25 + 0.25*puramp(it, 2.0, 0.0, 1.1) - - it->squish_control)) *65536.0); + squishright_i=(int)((middle+viswidth*(0.25f + 0.25f*puramp(it, 2.0f, 0.0f, 1.1f) + - it->squish_control)) *65536.0f); squishdiv=it->subwidth/15; rgb_start=raw_rgb_start+scl*3; @@ -1360,9 +1577,9 @@ analogtv_draw(analogtv *it) #ifdef DEBUG if (0) printf("scan %d: %0.3f %0.3f %0.3f scl=%d scr=%d scw=%d\n", lineno, - scanstart_i/65536.0, - squishright_i/65536.0, - scanend_i/65536.0, + scanstart_i/65536.0f, + squishright_i/65536.0f, + scanend_i/65536.0f, scl,scr,scw); #endif } @@ -1370,14 +1587,13 @@ analogtv_draw(analogtv *it) if (it->use_cmap) { for (y=ytop; ycontrast_control - * puramp(it, 1.0, 0.0, 1.0) / (0.5+0.5*puheight) * 0.070; - double levelmult_iq = levelmult * 0.090; + float levelmult=analogtv_levelmult(it, level); + float levelmult_y = levelmult * it->contrast_control + * puramp(it, 1.0f, 0.0f, 1.0f) / (0.5f+0.5f*it->puheight) * 0.070f; + float levelmult_iq = levelmult * 0.090f; - struct analogtv_yiq_s *yiq=it->yiq; analogtv_ntsc_to_yiq(it, lineno, signal, - (scanstart_i>>16)-10, (scanend_i>>16)+10); + (scanstart_i>>16)-10, (scanend_i>>16)+10, yiq); pixmultinc=pixrate; x=0; @@ -1389,21 +1605,21 @@ analogtv_draw(analogtv *it) } while (iusewidth) { - double pixfrac=(i&0xffff)/65536.0; - double invpixfrac=(1.0-pixfrac); + float pixfrac=(i&0xffff)/65536.0f; + float invpixfrac=(1.0f-pixfrac); int pati=i>>16; int yli,ili,qli,cmi; - double interpy=(yiq[pati].y*invpixfrac - + yiq[pati+1].y*pixfrac) * levelmult_y; - double interpi=(yiq[pati].i*invpixfrac - + yiq[pati+1].i*pixfrac) * levelmult_iq; - double interpq=(yiq[pati].q*invpixfrac - + yiq[pati+1].q*pixfrac) * levelmult_iq; + float interpy=(yiq[pati].y*invpixfrac + + yiq[pati+1].y*pixfrac) * levelmult_y; + float interpi=(yiq[pati].i*invpixfrac + + yiq[pati+1].i*pixfrac) * levelmult_iq; + float interpq=(yiq[pati].q*invpixfrac + + yiq[pati+1].q*pixfrac) * levelmult_iq; yli = (int)(interpy * it->cmap_y_levels); - ili = (int)((interpi+0.5) * it->cmap_i_levels); - qli = (int)((interpq+0.5) * it->cmap_q_levels); + ili = (int)((interpi+0.5f) * it->cmap_i_levels); + qli = (int)((interpq+0.5f) * it->cmap_q_levels); if (yli<0) yli=0; if (yli>=it->cmap_y_levels) yli=it->cmap_y_levels-1; if (ili<0) ili=0; @@ -1439,12 +1655,11 @@ analogtv_draw(analogtv *it) } } else { - struct analogtv_yiq_s *yiq=it->yiq; analogtv_ntsc_to_yiq(it, lineno, signal, - (scanstart_i>>16)-10, (scanend_i>>16)+10); + (scanstart_i>>16)-10, (scanend_i>>16)+10, yiq); - pixbright=it->contrast_control * puramp(it, 1.0, 0.0, 1.0) - / (0.5+0.5*puheight) * 1024.0/100.0; + pixbright=it->contrast_control * puramp(it, 1.0f, 0.0f, 1.0f) + / (0.5f+0.5f*it->puheight) * 1024.0f/100.0f; pixmultinc=pixrate; i=scanstart_i; rrp=rgb_start; while (i<0 && rrp!=rgb_end) { @@ -1453,14 +1668,14 @@ analogtv_draw(analogtv *it) rrp+=3; } while (i>16; - double r,g,b; + float r,g,b; - double interpy=(yiq[pati].y*invpixfrac + yiq[pati+1].y*pixfrac); - double interpi=(yiq[pati].i*invpixfrac + yiq[pati+1].i*pixfrac); - double interpq=(yiq[pati].q*invpixfrac + yiq[pati+1].q*pixfrac); + float interpy=(yiq[pati].y*invpixfrac + yiq[pati+1].y*pixfrac); + float interpi=(yiq[pati].i*invpixfrac + yiq[pati+1].i*pixfrac); + float interpq=(yiq[pati].q*invpixfrac + yiq[pati+1].q*pixfrac); /* According to the NTSC spec, Y,I,Q are generated as: @@ -1478,12 +1693,12 @@ analogtv_draw(analogtv *it) b = y - 1.105 i + 1.729 q */ - r=(interpy + 0.948*interpi + 0.624*interpq) * pixbright; - g=(interpy - 0.276*interpi - 0.639*interpq) * pixbright; - b=(interpy - 1.105*interpi + 1.729*interpq) * pixbright; - if (r<0.0) r=0.0; - if (g<0.0) g=0.0; - if (b<0.0) b=0.0; + r=(interpy + 0.948f*interpi + 0.624f*interpq) * pixbright; + g=(interpy - 0.276f*interpi - 0.639f*interpq) * pixbright; + b=(interpy - 1.105f*interpi + 1.729f*interpq) * pixbright; + if (r<0.0f) r=0.0f; + if (g<0.0f) g=0.0f; + if (b<0.0f) b=0.0f; rrp[0]=r; rrp[1]=g; rrp[2]=b; @@ -1496,7 +1711,7 @@ analogtv_draw(analogtv *it) rrp+=3; } while (rrp != rgb_end) { - rrp[0]=rrp[1]=rrp[2]=0.0; + rrp[0]=rrp[1]=rrp[2]=0.0f; rrp+=3; } @@ -1504,7 +1719,178 @@ analogtv_draw(analogtv *it) ytop,ybot); } } + free(raw_rgb_start); +} + +void +analogtv_draw(analogtv *it, double noiselevel, + const analogtv_reception *const *recs, unsigned rec_count) +{ + int i,lineno; + int /*bigloadchange,*/drawcount; + double baseload; + int overall_top, overall_bot; + + /* AnalogTV isn't very interesting if there isn't enough RAM. */ + if (!it->image) + return; + + it->rx_signal_level = noiselevel; + for (i = 0; i != rec_count; ++i) { + const analogtv_reception *rec = recs[i]; + double level = rec->level; + analogtv_input *inp=rec->input; + + it->rx_signal_level = + sqrt(it->rx_signal_level * it->rx_signal_level + + (level * level * (1.0 + 4.0*(rec->ghostfir[0] + rec->ghostfir[1] + + rec->ghostfir[2] + rec->ghostfir[3])))); + + /* duplicate the first line into the Nth line to ease wraparound computation */ + memcpy(inp->signal[ANALOGTV_V], inp->signal[0], + ANALOGTV_H * sizeof(inp->signal[0][0])); + } + + analogtv_setup_frame(it); + analogtv_set_demod(it); + + it->random0 = random(); + it->random1 = random(); + it->noiselevel = noiselevel; + it->recs = recs; + it->rec_count = rec_count; + threadpool_run(&it->threads, analogtv_thread_add_signals); + threadpool_wait(&it->threads); + + it->channel_change_cycles=0; + + /* rx_signal has an extra 2 lines at the end, where we copy the + first 2 lines so we can index into it while only worrying about + wraparound on a per-line level */ + memcpy(&it->rx_signal[ANALOGTV_SIGNAL_LEN], + &it->rx_signal[0], + 2*ANALOGTV_H*sizeof(it->rx_signal[0])); + + /* Repeat for signal_subtotals. */ + + memcpy(&it->signal_subtotals[ANALOGTV_SIGNAL_LEN / ANALOGTV_SUBTOTAL_LEN], + &it->signal_subtotals[0], + (2*ANALOGTV_H/ANALOGTV_SUBTOTAL_LEN)*sizeof(it->signal_subtotals[0])); + + analogtv_sync(it); /* Requires the add_signals be complete. */ + + baseload=0.5; + /* if (it->hashnoise_on) baseload=0.5; */ + + /*bigloadchange=1;*/ + drawcount=0; + it->crtload[ANALOGTV_TOP-1]=baseload; + it->puheight = puramp(it, 2.0, 1.0, 1.3) * it->height_control * + (1.125 - 0.125*puramp(it, 2.0, 2.0, 1.1)); + + analogtv_setup_levels(it, it->puheight * (double)it->useheight/(double)ANALOGTV_VISLINES); + + for (lineno=ANALOGTV_TOP; linenoshrinkpulse) { + baseload += 0.4; + /*bigloadchange=1;*/ + it->shrinkpulse=-1; + } + +#if 0 + if (it->hashnoise_rpm>0.0 && + !(bigloadchange || + it->redraw_all || + (slineno<20 && it->flutter_horiz_desync) || + it->gaussiannoise_level>30 || + ((it->gaussiannoise_level>2.0 || + it->multipath) && random()%4) || + linesig != it->onscreen_signature[lineno])) { + continue; + } + it->onscreen_signature[lineno] = linesig; +#endif + drawcount++; + + /* + Interpolate the 600-dotclock line into however many horizontal + screen pixels we're using, and convert to RGB. + + We add some 'bloom', variations in the horizontal scan width with + the amount of brightness, extremely common on period TV sets. They + had a single oscillator which generated both the horizontal scan and + (during the horizontal retrace interval) the high voltage for the + electron beam. More brightness meant more load on the oscillator, + which caused an decrease in horizontal deflection. Look for + (bloomthisrow). + + Also, the A2 did a bad job of generating horizontal sync pulses + during the vertical blanking interval. This, and the fact that the + horizontal frequency was a bit off meant that TVs usually went a bit + out of sync during the vertical retrace, and the top of the screen + would be bent a bit to the left or right. Look for (shiftthisrow). + + We also add a teeny bit of left overscan, just enough to be + annoying, but you can still read the left column of text. + + We also simulate compression & brightening on the right side of the + screen. Most TVs do this, but you don't notice because they overscan + so it's off the right edge of the CRT. But the A2 video system used + so much of the horizontal scan line that you had to crank the + horizontal width down in order to not lose the right few characters, + and you'd see the compression on the right edge. Associated with + compression is brightening; since the electron beam was scanning + slower, the same drive signal hit the phosphor harder. Look for + (squishright_i) and (squishdiv). + */ + + { + /* This used to be an int, I suspect by mistake. - Dave */ + float totsignal=0; + float ncl/*,diff*/; + unsigned frac; + size_t end0, end1; + const float *p; + + frac = signal_offset & (ANALOGTV_SUBTOTAL_LEN - 1); + p = it->rx_signal + (signal_offset & ~(ANALOGTV_SUBTOTAL_LEN - 1)); + for (i=0; i != frac; i++) { + totsignal -= p[i]; + } + + end0 = (signal_offset + ANALOGTV_PIC_LEN); + + end1 = end0 / ANALOGTV_SUBTOTAL_LEN; + for (i=signal_offset / ANALOGTV_SUBTOTAL_LEN; isignal_subtotals[i]; + } + + frac = end0 & (ANALOGTV_SUBTOTAL_LEN - 1); + p = it->rx_signal + (end0 & ~(ANALOGTV_SUBTOTAL_LEN - 1)); + for (i=0; i != frac; i++) { + totsignal += p[i]; + } + + totsignal *= it->agclevel; + ncl = 0.95f * it->crtload[lineno-1] + + 0.05f*(baseload + + (totsignal-30000)/100000.0f + + (slineno>184 ? (slineno-184)*(lineno-184)*0.001f * it->squeezebottom + : 0.0f)); + /*diff=ncl - it->crtload[lineno];*/ + /*bigloadchange = (diff>0.01 || diff<-0.01);*/ + it->crtload[lineno]=ncl; + } + } + + threadpool_run(&it->threads, analogtv_thread_draw_lines); + threadpool_wait(&it->threads); #if 0 /* poor attempt at visible retrace */ @@ -1528,6 +1914,18 @@ analogtv_draw(analogtv *it) it->need_clear=0; } + /* + Subtle change: overall_bot was the bottom of the last scan line. Now it's + the top of the next-after-the-last scan line. This is the same until + the y-dimension is > 2400, note ANALOGTV_MAX_LINEHEIGHT. + */ + + overall_top=(int)(it->useheight*(1-it->puheight)/2); + overall_bot=(int)(it->useheight*(1+it->puheight)/2); + + if (overall_top<0) overall_top=0; + if (overall_bot>it->useheight) overall_bot=it->useheight; + if (overall_top>0) { XClearArea(it->dpy, it->window, it->screen_xo, it->screen_yo, @@ -1720,101 +2118,6 @@ void analogtv_channel_noise(analogtv_input *it, analogtv_input *s2) #endif -void analogtv_add_signal(analogtv *it, analogtv_reception *rec) -{ - analogtv_input *inp=rec->input; - double *ps=it->rx_signal; - double *pe=it->rx_signal + ANALOGTV_SIGNAL_LEN; - double *p=ps; - signed char *ss=&inp->signal[0][0]; - signed char *se=&inp->signal[0][0] + ANALOGTV_SIGNAL_LEN; - signed char *s=ss + ((unsigned)rec->ofs % ANALOGTV_SIGNAL_LEN); - int i; - int ec=it->channel_change_cycles; - double level=rec->level; - double hfloss=rec->hfloss; - unsigned int fastrnd=random(); - double dp[8]; - - /* assert((se-ss)%4==0 && (se-s)%4==0); */ - - /* duplicate the first line into the Nth line to ease wraparound computation */ - memcpy(inp->signal[ANALOGTV_V], inp->signal[0], - ANALOGTV_H * sizeof(inp->signal[0][0])); - - for (i=0; i<8; i++) dp[i]=0.0; - - if (ec) { - double noise_ampl; - - /* Do a big noisy transition. We can make the transition noise of - high constant strength regardless of signal strength. - - There are two separate state machines. here, One is the noise - process and the other is the - - We don't bother with the FIR filter here - */ - - noise_ampl = 1.3; - - while (p!=pe && ec>0) { - - double sig0=(double)s[0]; - double noise = ((int)fastrnd-(int)0x7fffffff) * (50.0/(double)0x7fffffff); - fastrnd = (fastrnd*1103515245+12345) & 0xffffffffu; - - p[0] += sig0 * level * (1.0 - noise_ampl) + noise * noise_ampl; - - noise_ampl *= 0.99995; - - p++; - s++; - if (s>=se) s=ss; - ec--; - } - - } - - while (p != pe) { - double sig0,sig1,sig2,sig3,sigr; - - sig0=(double)s[0]; - sig1=(double)s[1]; - sig2=(double)s[2]; - sig3=(double)s[3]; - - dp[0]=sig0+sig1+sig2+sig3; - - /* Get the video out signal, and add some ghosting, typical of RF - monitor cables. This corresponds to a pretty long cable, but - looks right to me. - */ - - sigr=(dp[1]*rec->ghostfir[0] + dp[2]*rec->ghostfir[1] + - dp[3]*rec->ghostfir[2] + dp[4]*rec->ghostfir[3]); - dp[4]=dp[3]; dp[3]=dp[2]; dp[2]=dp[1]; dp[1]=dp[0]; - - p[0] += (sig0+sigr + sig2*hfloss) * level; - p[1] += (sig1+sigr + sig3*hfloss) * level; - p[2] += (sig2+sigr + sig0*hfloss) * level; - p[3] += (sig3+sigr + sig1*hfloss) * level; - - p += 4; - s += 4; - if (s>=se) s = ss + (s-se); - } - - it->rx_signal_level = - sqrt(it->rx_signal_level * it->rx_signal_level + - (level * level * (1.0 + 4.0*(rec->ghostfir[0] + rec->ghostfir[1] + - rec->ghostfir[2] + rec->ghostfir[3])))); - - - it->channel_change_cycles=0; - -} - #ifdef FIXME /* add hash */ if (it->hashnoise_times[lineno]) { @@ -1840,25 +2143,6 @@ void analogtv_add_signal(analogtv *it, analogtv_reception *rec) #endif -void analogtv_init_signal(analogtv *it, double noiselevel) -{ - double *ps=it->rx_signal; - double *pe=it->rx_signal + ANALOGTV_SIGNAL_LEN; - double *p=ps; - unsigned int fastrnd=random(); - double nm1=0.0,nm2=0.0; - double noisemul = sqrt(noiselevel*150)/(double)0x7fffffff; - - while (p != pe) { - nm2=nm1; - nm1 = ((int)fastrnd-(int)0x7fffffff) * noisemul; - *p++ = nm1*nm2; - fastrnd = (fastrnd*1103515245+12345) & 0xffffffffu; - } - - it->rx_signal_level = noiselevel; -} - void analogtv_reception_update(analogtv_reception *rec) {