Trevor Blackwell <tlb@tlb.org>
*/
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Intrinsic.h>
+/*
+ 2014-04-20, Dave Odell <dmo2118@gmail.com>:
+ API change: Folded analogtv_init_signal and *_add_signal into *_draw().
+ Added SMP support.
+ Replaced doubles with floats, including constants and transcendental functions.
+ Fixed a bug or two.
+*/
+
+/* 2015-02-27, Tomasz Sulej <tomeksul@gmail.com>:
+ - tint_control variable is used now
+ - removed unusable hashnoise code
+ */
+
+#ifdef HAVE_JWXYZ
+# include "jwxyz.h"
+#else /* !HAVE_JWXYZ */
+# include <X11/Xlib.h>
+# include <X11/Xutil.h>
+#endif
+#include <limits.h>
+
#include <assert.h>
+#include <errno.h>
#include "utils.h"
#include "resources.h"
#include "analogtv.h"
#include "yarandom.h"
#include "grabscreen.h"
+#include "visual.h"
/* #define DEBUG 1 */
-#ifdef DEBUG
+#if defined(DEBUG) && (defined(__linux) || defined(__FreeBSD__))
/* only works on linux + freebsd */
#include <machine/cpufunc.h>
#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;
}
char buf[256];
sprintf(buf,"%sTVTint",prefix);
- it->tint_control = get_float_resource(buf,"TVTint");
+ it->tint_control = get_float_resource(it->dpy, buf,"TVTint");
sprintf(buf,"%sTVColor",prefix);
- it->color_control = get_float_resource(buf,"TVColor")/100.0;
+ it->color_control = get_float_resource(it->dpy, buf,"TVColor")/100.0;
sprintf(buf,"%sTVBrightness",prefix);
- it->brightness_control = get_float_resource(buf,"TVBrightness") / 100.0;
+ it->brightness_control = get_float_resource(it->dpy, buf,"TVBrightness") / 100.0;
sprintf(buf,"%sTVContrast",prefix);
- it->contrast_control = get_float_resource(buf,"TVContrast") / 100.0;
+ it->contrast_control = get_float_resource(it->dpy, buf,"TVContrast") / 100.0;
it->height_control = 1.0;
it->width_control = 1.0;
it->squish_control = 0.0;
printf(" controls: tint=%g color=%g brightness=%g contrast=%g\n",
it->tint_control, it->color_control, it->brightness_control,
it->contrast_control);
- printf(" freq_error %g: %g %d\n",
- it->freq_error, it->freq_error_inc, it->flutter_tint);
+/* printf(" freq_error %g: %g %d\n",
+ it->freq_error, it->freq_error_inc, it->flutter_tint); */
printf(" desync: %g %d\n",
it->horiz_desync, it->flutter_horiz_desync);
printf(" hashnoise rpm: %g\n",
extern Bool mono_p; /* shoot me */
-void
+static void
analogtv_free_image(analogtv *it)
{
if (it->image) {
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;
}
}
-void
+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);
}
-void
+static void
analogtv_configure(analogtv *it)
{
int oldwidth=it->usewidth;
/* If the window is very small, don't let the image we draw get lower
than the actual TV resolution (266x200.)
- If the aspect ratio of the window is within 20% of a 4:3 ratio,
+ If the aspect ratio of the window is close to a 4:3 or 16:9 ratio,
then scale the image to exactly fill the window.
Otherwise, center the image either horizontally or vertically,
- padding on the left+right, or top+bottom, but not both.
+ letterboxing or pillarboxing (but not both).
If it's very close (2.5%) to a multiple of VISLINES, make it exact
For example, it maps 1024 => 1000.
*/
- float percent = 0.20;
- float min_ratio = 4.0 / 3.0 * (1 - percent);
- float max_ratio = 4.0 / 3.0 * (1 + percent);
+ float percent = 0.15;
+ float min_ratio = 4.0 / 3.0 * (1 - percent);
+ float max_ratio = 16.0 / 9.0 * (1 + percent);
float ratio;
float height_snap=0.025;
wlim = it->xgwa.width;
ratio = wlim / (float) hlim;
+#ifdef HAVE_MOBILE
+ /* Fill the whole iPhone screen, even though that distorts the image. */
+ min_ratio = 0;
+ max_ratio = 10;
+#endif
+
if (wlim < 266 || hlim < 200)
{
wlim = 266;
wlim, hlim, min_ratio, ratio, max_ratio);
# endif
}
- else if (ratio > max_ratio)
+ else if (ratio >= max_ratio)
{
wlim = hlim*max_ratio;
# ifdef DEBUG
min_ratio, ratio, max_ratio);
# endif
}
- else /* ratio < min_ratio */
+ else /* ratio <= min_ratio */
{
hlim = wlim/min_ratio;
# ifdef DEBUG
height_diff = ((hlim + ANALOGTV_VISLINES/2) % ANALOGTV_VISLINES) - ANALOGTV_VISLINES/2;
- if (height_diff != 0 && fabs(height_diff) < hlim * height_snap)
+ if (height_diff != 0 && abs(height_diff) < hlim * height_snap)
{
hlim -= height_diff;
}
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;
it->visbits=it->xgwa.visual->bits_per_rgb;
it->visdepth=it->xgwa.depth;
if (it->visclass == TrueColor || it->visclass == DirectColor) {
- if (get_integer_resource ("use_cmap", "Integer")) {
+ if (get_integer_resource (it->dpy, "use_cmap", "Integer")) {
it->use_cmap=1;
} else {
it->use_cmap=0;
/* Is there a standard way to do this? Does this handle all cases? */
int shift, prec;
for (shift=0; shift<32; shift++) {
- for (prec=1; prec<16 && prec<32-shift; prec++) {
+ for (prec=1; prec<16 && prec<40-shift; prec++) {
unsigned long mask=(0xffffUL>>(16-prec)) << shift;
if (it->red_shift<0 && mask==it->red_mask)
it->red_shift=shift, it->red_invprec=16-prec;
}
- gcv.background=get_pixel_resource("background", "Background",
- it->dpy, it->colormap);
+ gcv.background=get_pixel_resource(it->dpy, it->colormap,
+ "background", "Background");
it->gc = XCreateGC(it->dpy, it->window, GCBackground, &gcv);
XSetWindowBackground(it->dpy, it->window, gcv.background);
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;
}
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;
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);
}
*/
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];
{
colormode = (cb_i * cb_i + cb_q * cb_q) > 2.8;
if (colormode) {
- double tint_i = -cos((103 + it->color_control)*3.1415926/180);
- double tint_q = sin((103 + it->color_control)*3.1415926/180);
-
- multiq2[0] = (cb_i*tint_i - cb_q*tint_q) * it->color_control;
- multiq2[1] = (cb_q*tint_i + cb_i*tint_q) * it->color_control;
+ multiq2[0] = (cb_i*it->tint_i - cb_q*it->tint_q) * it->color_control;
+ multiq2[1] = (cb_q*it->tint_i + cb_i*it->tint_q) * it->color_control;
multiq2[2]=-multiq2[0];
multiq2[3]=-multiq2[1];
}
#endif
dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
- for (i=0; i<5; i++) dp[i]=0.0;
+ for (i=0; i<5; i++) dp[i]=0.0f;
assert(start>=0);
assert(end < ANALOGTV_PIC_LEN+10);
dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
for (i=0; i<24; i++) dp[i]=0.0;
- for (i=start, yiq=it->yiq+start, sp=signal+start;
+ for (i=start, yiq=it_yiq+start, sp=signal+start;
i<end;
i++, dp--, yiq++, sp++) {
mkfilter -Bu -Lp -o 4 -a 2.1428571429e-01 0 -Z 2.5e-01 -l
Delay about 2 */
- dp[0] = sp[0] * 0.0469904257251935 * agclevel;
- dp[8] = (+1.0*(dp[6]+dp[0])
- +4.0*(dp[5]+dp[1])
- +7.0*(dp[4]+dp[2])
- +8.0*(dp[3])
- -0.0176648*dp[12]
- -0.4860288*dp[10]);
+ dp[0] = sp[0] * 0.0469904257251935f * agclevel;
+ dp[8] = (+1.0f*(dp[6]+dp[0])
+ +4.0f*(dp[5]+dp[1])
+ +7.0f*(dp[4]+dp[2])
+ +8.0f*(dp[3])
+ -0.0176648f*dp[12]
+ -0.4860288f*dp[10]);
yiq->y = dp[8] + brightadd;
}
dp=delay+ANALOGTV_PIC_LEN-MAXDELAY;
for (i=0; i<27; i++) dp[i]=0.0;
- for (i=start, yiq=it->yiq+start, sp=signal+start;
+ for (i=start, yiq=it_yiq+start, sp=signal+start;
i<end;
i++, dp--, yiq++, sp++) {
- double sig=*sp;
+ float sig=*sp;
/* Filter I and Q with a 3-pole low-pass Butterworth filter at
1.5 MHz with an extra zero at 3.5 MHz, from
Delay about 3.
*/
- dp[0] = sig*multiq2[i&3] * 0.0833333333333;
+ dp[0] = sig*multiq2[i&3] * 0.0833333333333f;
yiq->i=dp[8] = (dp[5] + dp[0]
- +3.0*(dp[4] + dp[1])
- +4.0*(dp[3] + dp[2])
- -0.3333333333 * dp[10]);
+ +3.0f*(dp[4] + dp[1])
+ +4.0f*(dp[3] + dp[2])
+ -0.3333333333f * dp[10]);
- dp[16] = sig*multiq2[(i+3)&3] * 0.0833333333333;
+ dp[16] = sig*multiq2[(i+3)&3] * 0.0833333333333f;
yiq->q=dp[24] = (dp[16+5] + dp[16+0]
- +3.0*(dp[16+4] + dp[16+1])
- +4.0*(dp[16+3] + dp[16+2])
- -0.3333333333 * dp[24+2]);
+ +3.0f*(dp[16+4] + dp[16+1])
+ +4.0f*(dp[16+3] + dp[16+2])
+ -0.3333333333f * dp[24+2]);
}
} else {
- for (i=start, yiq=it->yiq+start; i<end; i++, yiq++) {
- yiq->i = yiq->q = 0.0;
+ for (i=start, yiq=it_yiq+start; i<end; i++, yiq++) {
+ yiq->i = yiq->q = 0.0f;
}
}
}
void
analogtv_setup_frame(analogtv *it)
{
- int i,x,y;
+ /* int i,x,y;*/
it->redraw_all=0;
((int)(random()&0xff)-0x80) * 0.000001;
}
+ /* it wasn't used
for (i=0; i<ANALOGTV_V; i++) {
it->hashnoise_times[i]=0;
}
+ */
+ /* let's leave it to process shrinkpulse */
if (it->hashnoise_enable && !it->hashnoise_on) {
if (random()%10000==0) {
it->hashnoise_on=1;
if (random()%1000==0) {
it->hashnoise_on=0;
}
+
+#if 0 /* never used */
if (it->hashnoise_on) {
it->hashnoise_rpm += (15000.0 - it->hashnoise_rpm)*0.05 +
((int)(random()%2000)-1000)*0.1;
}
if (it->hashnoise_rpm > 0.0) {
int hni;
+ double hni_double;
int hnc=it->hashnoise_counter; /* in 24.8 format */
/* Convert rpm of a 16-pole motor into dots in 24.8 format */
- hni = (int)(ANALOGTV_V * ANALOGTV_H * 256.0 /
- (it->hashnoise_rpm * 16.0 / 60.0 / 60.0));
+ hni_double = ANALOGTV_V * ANALOGTV_H * 256.0 /
+ (it->hashnoise_rpm * 16.0 / 60.0 / 60.0);
+ hni = (hni_double <= INT_MAX) ? (int)hni_double : INT_MAX;
while (hnc < (ANALOGTV_V * ANALOGTV_H)<<8) {
y=(hnc>>8)/ANALOGTV_H;
if (x>0 && x<ANALOGTV_H - ANALOGTV_HASHNOISE_LEN) {
it->hashnoise_times[y]=x;
}
- hnc += hni + (int)(random()%65536)-32768;
+ /* hnc += hni + (int)(random()%65536)-32768; */
+ {
+ hnc += (int)(random()%65536)-32768;
+ if ((hnc >= 0) && (INT_MAX - hnc < hni)) break;
+ hnc += hni;
+ }
}
- hnc -= (ANALOGTV_V * ANALOGTV_H)<<8;
}
+#endif /* 0 */
+
+/* hnc -= (ANALOGTV_V * ANALOGTV_H)<<8;*/
+
if (it->rx_signal_level != 0.0)
it->agclevel = 1.0/it->rx_signal_level;
analogtv_setup_sync(analogtv_input *input, int do_cb, int do_ssavi)
{
int i,lineno,vsync;
- char *sig;
+ signed char *sig;
int synclevel = do_ssavi ? ANALOGTV_WHITE_LEVEL : ANALOGTV_SYNC_LEVEL;
}
}
-void
+static void
analogtv_sync(analogtv *it)
{
int cur_hsync=it->cur_hsync;
int cur_vsync=it->cur_vsync;
- int lineno;
+ 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;
+/* sp = it->rx_signal + lineno*ANALOGTV_H + cur_hsync;*/
for (i=-32; i<32; i++) {
lineno = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V;
sp = it->rx_signal + lineno*ANALOGTV_H;
- filt=0.0;
+ filt=0.0f;
for (j=0; j<ANALOGTV_H; j+=ANALOGTV_H/16) {
filt += sp[j];
}
filt *= it->agclevel;
- osc = (double)(ANALOGTV_V+i)/(double)ANALOGTV_V;
+ osc = (float)(ANALOGTV_V+i)/(float)ANALOGTV_V;
- if (osc >= 1.05+0.0002 * filt) break;
+ if (osc >= 1.05f+0.0002f * filt) break;
}
cur_vsync = (cur_vsync + i + ANALOGTV_V) % ANALOGTV_V;
for (lineno=0; lineno<ANALOGTV_V; lineno++) {
if (lineno>5 && lineno<ANALOGTV_V-3) { /* ignore vsync interval */
-
- sp = it->rx_signal + ((lineno + cur_vsync + ANALOGTV_V)%ANALOGTV_V
- )*ANALOGTV_H + cur_hsync;
+ unsigned lineno2 = (lineno + cur_vsync + ANALOGTV_V)%ANALOGTV_V;
+ if (!lineno2) lineno2 = ANALOGTV_V;
+ sp = it->rx_signal + lineno2*ANALOGTV_H + cur_hsync;
for (i=-8; i<8; i++) {
- osc = (double)(ANALOGTV_H+i)/(double)ANALOGTV_H;
+ osc = (float)(ANALOGTV_H+i)/(float)ANALOGTV_H;
filt=(sp[i-3]+sp[i-2]+sp[i-1]+sp[i]) * it->agclevel;
- if (osc >= 1.005 + 0.0001*filt) break;
+ if (osc >= 1.005f + 0.0001f*filt) break;
}
cur_hsync = (cur_hsync + i + ANALOGTV_H) % ANALOGTV_H;
}
if (lineno>15) {
sp = it->rx_signal + lineno*ANALOGTV_H + (cur_hsync&~3);
for (i=ANALOGTV_CB_START+8; i<ANALOGTV_CB_START+36-8; i++) {
- it->cb_phase[i&3] = it->cb_phase[i&3]*(1.0-cbfc) +
+ it->cb_phase[i&3] = it->cb_phase[i&3]*(1.0f-cbfc) +
sp[i]*it->agclevel*cbfc;
}
}
{
- double tot=0.1;
- double cbgain;
+ float tot=0.1f;
+ float cbgain;
for (i=0; i<4; i++) {
tot += it->cb_phase[i]*it->cb_phase[i];
}
- cbgain = 32.0/sqrt(tot);
+ cbgain = 32.0f/sqrtf(tot);
for (i=0; i<4; i++) {
it->line_cb_phase[lineno][i]=it->cb_phase[i]*cbgain;
}
static double
-analogtv_levelmult(analogtv *it, int level)
+analogtv_levelmult(const analogtv *it, int level)
{
- static double levelfac[3]={-7.5, 5.5, 24.5};
+ 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) {
}
/*
-
The point of this stuff is to ensure that when useheight is not a
multiple of VISLINES so that TV scan lines map to different numbers
of vertical screen pixels, the total brightness of each scan line
remains the same.
- MAX_LINEHEIGHT corresponds to 2400 vertical pixels, beyond which
+ ANALOGTV_MAX_LINEHEIGHT corresponds to 2400 vertical pixels, beyond which
it interpolates extra black lines.
*/
-enum {MAX_LINEHEIGHT=12};
-static struct {
- int index;
- double value;
-} leveltable[MAX_LINEHEIGHT+1][MAX_LINEHEIGHT+1];
static void
analogtv_setup_levels(analogtv *it, double avgheight)
{
int i,height;
- static double levelfac[3]={-7.5, 5.5, 24.5};
+ static const double levelfac[3]={-7.5, 5.5, 24.5};
- for (height=0; height<avgheight+2.0 && height<=MAX_LINEHEIGHT; height++) {
+ for (height=0; height<avgheight+2.0 && height<=ANALOGTV_MAX_LINEHEIGHT; height++) {
for (i=0; i<height; i++) {
- leveltable[height][i].index = 2;
+ it->leveltable[height][i].index = 2;
}
if (avgheight>=3) {
- leveltable[height][0].index=0;
+ it->leveltable[height][0].index=0;
}
if (avgheight>=5) {
- leveltable[height][height-1].index=0;
+ if (height >= 1) it->leveltable[height][height-1].index=0;
}
if (avgheight>=7) {
- leveltable[height][1].index=1;
- leveltable[height][height-2].index=1;
+ it->leveltable[height][1].index=1;
+ if (height >= 2) it->leveltable[height][height-2].index=1;
}
for (i=0; i<height; i++) {
- leveltable[height][i].value =
- (40.0 + levelfac[leveltable[height][i].index]*puramp(it, 3.0, 6.0, 1.0)) / 256.0;
+ it->leveltable[height][i].value =
+ (40.0 + levelfac[it->leveltable[height][i].index]*puramp(it, 3.0, 6.0, 1.0)) / 256.0;
+ }
+
+ }
+}
+
+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);
+ unsigned int fastrnd_offset;
+ float nm1,nm2;
+ float noisemul = sqrt(noiselevel*150)/(float)0x7fffffff;
+
+ fastrnd_offset = fastrnd - 0x7fffffff;
+ nm1 = (fastrnd_offset <= INT_MAX ? (int)fastrnd_offset : -1 - (int)(UINT_MAX - fastrnd_offset)) * noisemul;
+ while (p != pe) {
+ nm2=nm1;
+ fastrnd = (fastrnd*FASTRND_A+FASTRND_C) & 0xffffffffu;
+ fastrnd_offset = fastrnd - 0x7fffffff;
+ nm1 = (fastrnd_offset <= INT_MAX ? (int)fastrnd_offset : -1 - (int)(UINT_MAX - fastrnd_offset)) * 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];
+ unsigned int fastrnd_offset = fastrnd - 0x7fffffff;
+ float noise = (fastrnd_offset <= INT_MAX ? (int)fastrnd_offset : -1 - (int)(UINT_MAX - fastrnd_offset)) * (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)
{
float *rpf;
char *level_copyfrom[3];
int xrepl=it->xrepl;
+ unsigned lineheight = ybot - ytop;
+ if (lineheight > ANALOGTV_MAX_LINEHEIGHT) lineheight = ANALOGTV_MAX_LINEHEIGHT;
for (i=0; i<3; i++) level_copyfrom[i]=NULL;
for (y=ytop; y<ybot; y++) {
- int level=leveltable[ybot-ytop][y-ytop].index;
- double levelmult=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
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;
}
}
-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;
-
- float *raw_rgb_start=(float *)calloc(it->subwidth*3, sizeof(float));
- float *raw_rgb_end=raw_rgb_start+3*it->subwidth;
- float *rrp;
+ const analogtv_thread *thread = (analogtv_thread *)thread_raw;
+ const analogtv *it = thread->it;
- analogtv_setup_frame(it);
- analogtv_set_demod(it);
-
- /* 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]));
+ int lineno;
- analogtv_sync(it);
+ float *raw_rgb_start;
+ float *raw_rgb_end;
+ raw_rgb_start=(float *)calloc(it->subwidth*3, sizeof(float));
- baseload=0.5;
- /* if (it->hashnoise_on) baseload=0.5; */
+ if (! raw_rgb_start) return;
- 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));
+ raw_rgb_end=raw_rgb_start+3*it->subwidth;
- analogtv_setup_levels(it, puheight * (double)it->useheight/(double)ANALOGTV_VISLINES);
+ for (lineno=ANALOGTV_TOP + thread->thread_id;
+ lineno<ANALOGTV_BOT;
+ lineno += it->threads.count) {
+ int i,j,x,y;
- overall_top=it->useheight;
- overall_bot=0;
+ int slineno, ytop, ybot;
+ unsigned signal_offset;
- for (lineno=ANALOGTV_TOP; lineno<ANALOGTV_BOT; lineno++) {
- int slineno=lineno-ANALOGTV_TOP;
- int ytop=(int)((slineno*it->useheight/ANALOGTV_VISLINES -
- it->useheight/2)*puheight) + it->useheight/2;
- int ybot=(int)(((slineno+1)*it->useheight/ANALOGTV_VISLINES -
- it->useheight/2)*puheight) + it->useheight/2;
-#if 0
- int linesig=analogtv_line_signature(input,lineno)
- + it->hashnoise_times[lineno];
-#endif
- double *signal=(it->rx_signal + ((lineno + it->cur_vsync +
- ANALOGTV_V)%ANALOGTV_V) * ANALOGTV_H +
- it->line_hsync[lineno]);
+ const float *signal;
- if (ytop==ybot) continue;
- if (ybot<0 || ytop>it->useheight) continue;
- if (ytop<0) ytop=0;
- if (ybot>it->useheight) ybot=it->useheight;
+ int scanstart_i,scanend_i,squishright_i,squishdiv,pixrate;
+ float *rgb_start, *rgb_end;
+ float pixbright;
+ int pixmultinc;
- if (ybot > ytop+MAX_LINEHEIGHT) ybot=ytop+MAX_LINEHEIGHT;
+ float *rrp;
- if (ytop < overall_top) overall_top=ytop;
- if (ybot > overall_bot) overall_bot=ybot;
+ struct analogtv_yiq_s yiq[ANALOGTV_PIC_LEN+10];
- if (lineno==it->shrinkpulse) {
- baseload += 0.4;
- bigloadchange=1;
- it->shrinkpulse=-1;
- }
-
-#if 0
- if (it->hashnoise_rpm>0.0 &&
- !(bigloadchange ||
- it->redraw_all ||
- (slineno<20 && it->flutter_horiz_desync) ||
- it->gaussiannoise_level>30 ||
- ((it->gaussiannoise_level>2.0 ||
- it->multipath) && random()%4) ||
- linesig != it->onscreen_signature[lineno])) {
+ if (! analogtv_get_line(it, lineno, &slineno, &ytop, &ybot,
+ &signal_offset))
continue;
- }
- it->onscreen_signature[lineno] = linesig;
-#endif
- drawcount++;
-
- /*
- Interpolate the 600-dotclock line into however many horizontal
- screen pixels we're using, and convert to RGB.
-
- We add some 'bloom', variations in the horizontal scan width with
- the amount of brightness, extremely common on period TV sets. They
- had a single oscillator which generated both the horizontal scan and
- (during the horizontal retrace interval) the high voltage for the
- electron beam. More brightness meant more load on the oscillator,
- which caused an decrease in horizontal deflection. Look for
- (bloomthisrow).
-
- Also, the A2 did a bad job of generating horizontal sync pulses
- during the vertical blanking interval. This, and the fact that the
- horizontal frequency was a bit off meant that TVs usually went a bit
- out of sync during the vertical retrace, and the top of the screen
- would be bent a bit to the left or right. Look for (shiftthisrow).
- We also add a teeny bit of left overscan, just enough to be
- annoying, but you can still read the left column of text.
-
- We also simulate compression & brightening on the right side of the
- screen. Most TVs do this, but you don't notice because they overscan
- so it's off the right edge of the CRT. But the A2 video system used
- so much of the horizontal scan line that you had to crank the
- horizontal width down in order to not lose the right few characters,
- and you'd see the compression on the right edge. Associated with
- compression is brightening; since the electron beam was scanning
- slower, the same drive signal hit the phosphor harder. Look for
- (squishright_i) and (squishdiv).
- */
+ signal = it->rx_signal + signal_offset;
{
- int totsignal=0;
- double ncl,diff;
- for (i=0; i<ANALOGTV_PIC_LEN; i++) {
- totsignal += signal[i];
- }
- totsignal *= it->agclevel;
- ncl = 0.95 * it->crtload[lineno-1] +
- 0.05*(baseload +
- (totsignal-30000)/100000.0 +
- (slineno>184 ? (slineno-184)*(lineno-184)*0.001 * it->squeezebottom
- : 0.0));
- diff=ncl - it->crtload[lineno];
- bigloadchange = (diff>0.01 || diff<-0.01);
- it->crtload[lineno]=ncl;
- }
-
- {
- double bloomthisrow,shiftthisrow;
- double viswidth,middle;
- double scanwidth;
+ float bloomthisrow,shiftthisrow;
+ float viswidth,middle;
+ float scanwidth;
int scw,scl,scr;
- bloomthisrow = -10.0 * it->crtload[lineno];
- if (bloomthisrow<-10.0) bloomthisrow=-10.0;
- if (bloomthisrow>2.0) bloomthisrow=2.0;
+ bloomthisrow = -10.0f * it->crtload[lineno];
+ if (bloomthisrow<-10.0f) bloomthisrow=-10.0f;
+ if (bloomthisrow>2.0f) bloomthisrow=2.0f;
if (slineno<16) {
- shiftthisrow=it->horiz_desync * (exp(-0.17*slineno) *
- (0.7+cos(slineno*0.6)));
+ shiftthisrow=it->horiz_desync * (expf(-0.17f*slineno) *
+ (0.7f+cosf(slineno*0.6f)));
} else {
- shiftthisrow=0.0;
+ shiftthisrow=0.0f;
}
- viswidth=ANALOGTV_PIC_LEN * 0.79 - 5.0*bloomthisrow;
+ viswidth=ANALOGTV_PIC_LEN * 0.79f - 5.0f*bloomthisrow;
middle=ANALOGTV_PIC_LEN/2 - shiftthisrow;
- scanwidth=it->width_control * puramp(it, 0.5, 0.3, 1.0);
+ scanwidth=it->width_control * puramp(it, 0.5f, 0.3f, 1.0f);
scw=it->subwidth*scanwidth;
if (scw>it->subwidth) scw=it->usewidth;
scl=it->subwidth/2 - scw/2;
scr=it->subwidth/2 + scw/2;
- pixrate=(int)((viswidth*65536.0*1.0)/it->subwidth)/scanwidth;
- scanstart_i=(int)((middle-viswidth*0.5)*65536.0);
+ pixrate=(int)((viswidth*65536.0f*1.0f)/it->subwidth)/scanwidth;
+ scanstart_i=(int)((middle-viswidth*0.5f)*65536.0f);
scanend_i=(ANALOGTV_PIC_LEN-1)*65536;
- squishright_i=(int)((middle+viswidth*(0.25 + 0.25*puramp(it, 2.0, 0.0, 1.1)
- - it->squish_control)) *65536.0);
+ squishright_i=(int)((middle+viswidth*(0.25f + 0.25f*puramp(it, 2.0f, 0.0f, 1.1f)
+ - it->squish_control)) *65536.0f);
squishdiv=it->subwidth/15;
rgb_start=raw_rgb_start+scl*3;
#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
}
if (it->use_cmap) {
for (y=ytop; y<ybot; y++) {
int level=analogtv_level(it, y, ytop, ybot);
- double levelmult=analogtv_levelmult(it, level);
- double levelmult_y = levelmult * it->contrast_control
- * puramp(it, 1.0, 0.0, 1.0) / (0.5+0.5*puheight) * 0.070;
- double levelmult_iq = levelmult * 0.090;
+ float levelmult=analogtv_levelmult(it, level);
+ float levelmult_y = levelmult * it->contrast_control
+ * puramp(it, 1.0f, 0.0f, 1.0f) / (0.5f+0.5f*it->puheight) * 0.070f;
+ float levelmult_iq = levelmult * 0.090f;
- struct analogtv_yiq_s *yiq=it->yiq;
analogtv_ntsc_to_yiq(it, lineno, signal,
- (scanstart_i>>16)-10, (scanend_i>>16)+10);
+ (scanstart_i>>16)-10, (scanend_i>>16)+10, yiq);
pixmultinc=pixrate;
x=0;
}
while (i<scanend_i && x<it->usewidth) {
- double pixfrac=(i&0xffff)/65536.0;
- double invpixfrac=(1.0-pixfrac);
+ float pixfrac=(i&0xffff)/65536.0f;
+ float invpixfrac=(1.0f-pixfrac);
int pati=i>>16;
int yli,ili,qli,cmi;
- double interpy=(yiq[pati].y*invpixfrac
- + yiq[pati+1].y*pixfrac) * levelmult_y;
- double interpi=(yiq[pati].i*invpixfrac
- + yiq[pati+1].i*pixfrac) * levelmult_iq;
- double interpq=(yiq[pati].q*invpixfrac
- + yiq[pati+1].q*pixfrac) * levelmult_iq;
+ float interpy=(yiq[pati].y*invpixfrac
+ + yiq[pati+1].y*pixfrac) * levelmult_y;
+ float interpi=(yiq[pati].i*invpixfrac
+ + yiq[pati+1].i*pixfrac) * levelmult_iq;
+ float interpq=(yiq[pati].q*invpixfrac
+ + yiq[pati+1].q*pixfrac) * levelmult_iq;
yli = (int)(interpy * it->cmap_y_levels);
- ili = (int)((interpi+0.5) * it->cmap_i_levels);
- qli = (int)((interpq+0.5) * it->cmap_q_levels);
+ ili = (int)((interpi+0.5f) * it->cmap_i_levels);
+ qli = (int)((interpq+0.5f) * it->cmap_q_levels);
if (yli<0) yli=0;
if (yli>=it->cmap_y_levels) yli=it->cmap_y_levels-1;
if (ili<0) ili=0;
}
}
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) {
rrp+=3;
}
while (i<scanend_i && rrp!=rgb_end) {
- double pixfrac=(i&0xffff)/65536.0;
- double invpixfrac=1.0-pixfrac;
+ float pixfrac=(i&0xffff)/65536.0f;
+ float invpixfrac=1.0f-pixfrac;
int pati=i>>16;
- double r,g,b;
+ float r,g,b;
- double interpy=(yiq[pati].y*invpixfrac + yiq[pati+1].y*pixfrac);
- double interpi=(yiq[pati].i*invpixfrac + yiq[pati+1].i*pixfrac);
- double interpq=(yiq[pati].q*invpixfrac + yiq[pati+1].q*pixfrac);
+ float interpy=(yiq[pati].y*invpixfrac + yiq[pati+1].y*pixfrac);
+ float interpi=(yiq[pati].i*invpixfrac + yiq[pati+1].i*pixfrac);
+ float interpq=(yiq[pati].q*invpixfrac + yiq[pati+1].q*pixfrac);
/*
According to the NTSC spec, Y,I,Q are generated as:
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;
rrp+=3;
}
while (rrp != rgb_end) {
- rrp[0]=rrp[1]=rrp[2]=0.0;
+ rrp[0]=rrp[1]=rrp[2]=0.0f;
rrp+=3;
}
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);
+
+ /* calculate tint once per frame */
+ it->tint_i = -cos((103 + it->tint_control)*3.1415926/180);
+ it->tint_q = sin((103 + it->tint_control)*3.1415926/180);
+
+ for (lineno=ANALOGTV_TOP; lineno<ANALOGTV_BOT; lineno++) {
+ int slineno, ytop, ybot;
+ unsigned signal_offset;
+ if (! analogtv_get_line(it, lineno, &slineno, &ytop, &ybot, &signal_offset))
+ continue;
+
+ if (lineno==it->shrinkpulse) {
+ baseload += 0.4;
+ /*bigloadchange=1;*/
+ it->shrinkpulse=-1;
+ }
+
+#if 0
+ if (it->hashnoise_rpm>0.0 &&
+ !(bigloadchange ||
+ it->redraw_all ||
+ (slineno<20 && it->flutter_horiz_desync) ||
+ it->gaussiannoise_level>30 ||
+ ((it->gaussiannoise_level>2.0 ||
+ it->multipath) && random()%4) ||
+ linesig != it->onscreen_signature[lineno])) {
+ continue;
+ }
+ it->onscreen_signature[lineno] = linesig;
+#endif
+ /* drawcount++;*/
+
+ /*
+ Interpolate the 600-dotclock line into however many horizontal
+ screen pixels we're using, and convert to RGB.
+
+ We add some 'bloom', variations in the horizontal scan width with
+ the amount of brightness, extremely common on period TV sets. They
+ had a single oscillator which generated both the horizontal scan and
+ (during the horizontal retrace interval) the high voltage for the
+ electron beam. More brightness meant more load on the oscillator,
+ which caused an decrease in horizontal deflection. Look for
+ (bloomthisrow).
+
+ Also, the A2 did a bad job of generating horizontal sync pulses
+ during the vertical blanking interval. This, and the fact that the
+ horizontal frequency was a bit off meant that TVs usually went a bit
+ out of sync during the vertical retrace, and the top of the screen
+ would be bent a bit to the left or right. Look for (shiftthisrow).
+
+ We also add a teeny bit of left overscan, just enough to be
+ annoying, but you can still read the left column of text.
+
+ We also simulate compression & brightening on the right side of the
+ screen. Most TVs do this, but you don't notice because they overscan
+ so it's off the right edge of the CRT. But the A2 video system used
+ so much of the horizontal scan line that you had to crank the
+ horizontal width down in order to not lose the right few characters,
+ and you'd see the compression on the right edge. Associated with
+ compression is brightening; since the electron beam was scanning
+ slower, the same drive signal hit the phosphor harder. Look for
+ (squishright_i) and (squishdiv).
+ */
+
+ {
+ /* This used to be an int, I suspect by mistake. - Dave */
+ float totsignal=0;
+ float ncl/*,diff*/;
+ unsigned frac;
+ size_t end0, end1;
+ const float *p;
+
+ frac = signal_offset & (ANALOGTV_SUBTOTAL_LEN - 1);
+ p = it->rx_signal + (signal_offset & ~(ANALOGTV_SUBTOTAL_LEN - 1));
+ for (i=0; i != frac; i++) {
+ totsignal -= p[i];
+ }
+
+ end0 = (signal_offset + ANALOGTV_PIC_LEN);
+
+ end1 = end0 / ANALOGTV_SUBTOTAL_LEN;
+ for (i=signal_offset / ANALOGTV_SUBTOTAL_LEN; i<end1; i++) {
+ totsignal += it->signal_subtotals[i];
+ }
+
+ frac = end0 & (ANALOGTV_SUBTOTAL_LEN - 1);
+ p = it->rx_signal + (end0 & ~(ANALOGTV_SUBTOTAL_LEN - 1));
+ for (i=0; i != frac; i++) {
+ totsignal += p[i];
+ }
+
+ totsignal *= it->agclevel;
+ ncl = 0.95f * it->crtload[lineno-1] +
+ 0.05f*(baseload +
+ (totsignal-30000)/100000.0f +
+ (slineno>184 ? (slineno-184)*(lineno-184)*0.001f * it->squeezebottom
+ : 0.0f));
+ /*diff=ncl - it->crtload[lineno];*/
+ /*bigloadchange = (diff>0.01 || diff<-0.01);*/
+ it->crtload[lineno]=ncl;
+ }
+ }
+
+ threadpool_run(&it->threads, analogtv_thread_draw_lines);
+ threadpool_wait(&it->threads);
#if 0
/* poor attempt at visible retrace */
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,
it->last_display_time=tv;
}
#endif
-
- XSync(it->dpy,0);
}
analogtv_input *
img_w=pic_im->width;
img_h=pic_im->height;
-
+
for (i=0; i<ANALOGTV_PIC_LEN+4; i++) {
double phase=90.0-90.0*i;
double ampl=1.0;
#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;
- char *ss=&inp->signal[0][0];
- char *se=&inp->signal[0][0] + ANALOGTV_SIGNAL_LEN;
- 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]) {
#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)
{
}
+/* jwz: since MacOS doesn't have "6x10", I dumped this font to an XBM...
+ */
+
+#include "images/6x10font.xbm"
+
void
analogtv_make_font(Display *dpy, Window window, analogtv_font *f,
int w, int h, char *fontname)
XGetWindowAttributes (dpy, window, &xgwa);
- if (fontname) {
+ if (fontname && !strcmp (fontname, "6x10")) {
+
+ text_pm = XCreatePixmapFromBitmapData (dpy, window,
+ (char *) font6x10_bits,
+ font6x10_width,
+ font6x10_height,
+ 1, 0, 1);
+ f->text_im = XGetImage(dpy, text_pm, 0, 0, font6x10_width, font6x10_height,
+ 1, XYPixmap);
+ XFreePixmap(dpy, text_pm);
+
+ } else if (fontname) {
font = XLoadQueryFont (dpy, fontname);
if (!font) {
abort();
}
- text_pm=XCreatePixmap(dpy, window, 128*f->char_w, f->char_h, xgwa.depth);
+ text_pm=XCreatePixmap(dpy, window, 256*f->char_w, f->char_h, 1);
memset(&gcv, 0, sizeof(gcv));
gcv.foreground=1;
gc=XCreateGC(dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
XSetForeground(dpy, gc, 0);
- XFillRectangle(dpy, text_pm, gc, 0, 0, 128*f->char_w, f->char_h);
+ XFillRectangle(dpy, text_pm, gc, 0, 0, 256*f->char_w, f->char_h);
XSetForeground(dpy, gc, 1);
- /* Just ASCII */
- for (i=0; i<128; i++) {
+ for (i=0; i<256; i++) {
char c=i;
int x=f->char_w*i+1;
int y=f->char_h*8/10;
XDrawString(dpy, text_pm, gc, x, y, &c, 1);
}
- f->text_im = XGetImage(dpy, text_pm, 0, 0, 128*f->char_w, f->char_h,
- ~0L, ZPixmap);
+ f->text_im = XGetImage(dpy, text_pm, 0, 0, 256*f->char_w, f->char_h,
+ 1, XYPixmap);
+# if 0
+ XWriteBitmapFile(dpy, "/tmp/tvfont.xbm", text_pm,
+ 256*f->char_w, f->char_h, -1, -1);
+# endif
XFreeGC(dpy, gc);
XFreePixmap(dpy, text_pm);
} else {
- f->text_im = XCreateImage(dpy, xgwa.visual, xgwa.depth,
- ZPixmap, 0, 0,
- 128*f->char_w, f->char_h, 8, 0);
+ f->text_im = XCreateImage(dpy, xgwa.visual, 1, XYPixmap, 0, 0,
+ 256*f->char_w, f->char_h, 8, 0);
f->text_im->data = (char *)calloc(f->text_im->height,
f->text_im->bytes_per_line);
{
if (x<0 || x>=f->char_w) return 0;
if (y<0 || y>=f->char_h) return 0;
- if (c<0 || c>=128) return 0;
+ if (c<0 || c>=256) return 0;
return XGetPixel(f->text_im, c*f->char_w + x, y) ? 1 : 0;
}
{
if (x<0 || x>=f->char_w) return;
if (y<0 || y>=f->char_h) return;
- if (c<0 || c>=128) return;
+ if (c<0 || c>=256) return;
XPutPixel(f->text_im, c*f->char_w + x, y, value);
}
{
int value,x,y;
- if (c<0 || c>=128) return;
+ if (c<0 || c>=256) return;
for (y=0; y<f->char_h; y++) {
for (x=0; x<f->char_w; x++) {
}
}
}
-
-extern XtAppContext app;
-
-int
-analogtv_handle_events (analogtv *it)
-{
- XSync(it->dpy, False);
- if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
- XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
-
- while (XPending (it->dpy))
- {
- XEvent event;
- XNextEvent (it->dpy, &event);
- switch (event.xany.type)
- {
- case ButtonPress:
- return 1;
-
- case KeyPress:
- {
- KeySym keysym;
- char c = 0;
-
- if (it->key_handler) {
- if (it->key_handler (it->dpy, &event, it->key_data))
- return 1;
- } else {
- XLookupString (&event.xkey, &c, 1, &keysym, 0);
- if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
- return 1;
- }
- }
- break;
-
- /* I don't seem to get an event when clicking the "full
- screen" window manager icon, at least when using
- metacity. Thus, it doesn't change the video size. Is this
- some separate WM_* message I have to deal with?
- */
- case ConfigureNotify:
- if (event.xconfigure.width != it->xgwa.width ||
- event.xconfigure.height != it->xgwa.height)
- analogtv_reconfigure(it);
- break;
-
- case Expose:
- case GraphicsExpose:
- it->need_clear=1;
- break;
-
- default:
- break;
- }
- if (it->event_handler) {
- (*it->event_handler) (it->dpy, &event);
- }
- }
- return 0;
-}
-