fed55dce4f70f9c28bf9abc6279490e3333c73a5
[xscreensaver] / hacks / apple2.c
1 /* xscreensaver, Copyright (c) 1998-2004 Jamie Zawinski <jwz@jwz.org>
2  *
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
9  * implied warranty.
10  *
11  * Apple ][ CRT simulator, by Trevor Blackwell <tlb@tlb.org>
12  * with additional work by Jamie Zawinski <jwz@jwz.org>
13  */
14
15 #include <math.h>
16 #include "screenhack.h"
17 #include "apple2.h"
18 #include <time.h>
19 #include <sys/time.h>
20 #include <X11/Intrinsic.h>
21
22 #ifdef HAVE_XSHM_EXTENSION
23 #include "xshm.h"
24 #endif
25
26 #define DEBUG
27
28 extern XtAppContext app;
29
30 /*
31  * Implementation notes
32  *
33  * The A2 had 3 display modes: text, lores, and hires. Text was 40x24, and it
34  * disabled color in the TV. Lores gave you 40x48 graphics blocks, using the
35  * same memory as the text screen. Each could be one of 16 colors. Hires gave
36  * you 280x192 pixels. Odd pixels were blue or purple, and even pixels were
37  * orange or green depending on the setting of the high bit in each byte.
38  *
39  * The graphics modes could also have 4 lines of text at the bottom. This was
40  * fairly unreadable if you had a color monitor.
41  *
42  * Each mode had 2 different screens using different memory space. In hires
43  * mode this was sometimes used for double buffering, but more often the lower
44  * screen was full of code/data and the upper screen was used for display, so
45  * you got random garbage on the screen.
46  *
47  * The text font is based on X's standard 6x10 font, with a few tweaks like
48  * putting a slash across the zero.
49  *
50  * To use this, you'll call apple2(display, window, duration,
51  * controller) where the function controller defines what will happen.
52  * See bsod.c and apple2-main.c for example controllers. The
53  * controller function gets called whenever the machine ready to start
54  * something new. By setting sim->printing or sim->typing, it'll be
55  * busy for some time spitting characters out one at a time. By
56  * setting *next_actiontime+=X.X, it'll pause and just update the screen
57  * for that long before calling the controller function again.
58  *
59  * By setting stepno to A2CONTROLLER_DONE, the loop will end. It will also end
60  * after the time specified by the delay parameter. In either case, it calls
61  * the controller with stepno==A2CONTROLLER_FREE to allow it to release any
62  * memory.
63  *
64  * The void* apple2_sim_t::controller_data is for the use of the controller.
65  * It will be initialize to NULL, and the controller can store its own state
66  * there.
67  *
68  */
69
70 void
71 a2_scroll(apple2_state_t *st)
72 {
73   int i;
74   st->textlines[st->cursy][st->cursx] ^= 0xc0;     /* turn off cursor */
75   for (i=0; i<23; i++) {
76     memcpy(st->textlines[i],st->textlines[i+1],40);
77   }
78   memset(st->textlines[23],0xe0,40);
79   st->textlines[st->cursy][st->cursx] ^= 0xc0;     /* turn cursor back on */
80 }
81
82 static void
83 a2_printc_1(apple2_state_t *st, char c, int scroll_p)
84 {
85   st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
86
87   if (c == '\n')                      /* ^J == NL */
88     {
89       if (st->cursy==23)
90         {
91           if (scroll_p)
92             a2_scroll(st);
93         }
94       else
95         {
96           st->cursy++;
97         }
98       st->cursx=0;
99     }
100   else if (c == 014)                  /* ^L == CLS, Home */
101     {
102       a2_cls(st);
103       a2_goto(st,0,0);
104     }
105   else if (c == '\t')                 /* ^I == tab */
106     {
107       a2_goto(st, st->cursy, (st->cursx+8)&~7);
108     }
109   else if (c == 010)                  /* ^H == backspace */
110     {
111       st->textlines[st->cursy][st->cursx]=0xe0;
112       a2_goto(st, st->cursy, st->cursx-1);
113     }
114   else if (c == '\r')                 /* ^M == CR */
115     {
116       st->cursx=0;
117     }
118   else
119     {
120       st->textlines[st->cursy][st->cursx]=c ^ 0xc0;
121       st->cursx++;
122       if (st->cursx==40) {
123         if (st->cursy==23) {
124           if (scroll_p)
125             a2_scroll(st);
126         } else {
127           st->cursy++;
128         }
129         st->cursx=0;
130       }
131     }
132
133   st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
134 }
135
136 void
137 a2_printc(apple2_state_t *st, char c)
138 {
139   a2_printc_1(st, c, 1);
140 }
141
142 void
143 a2_printc_noscroll(apple2_state_t *st, char c)
144 {
145   a2_printc_1(st, c, 0);
146 }
147
148
149 void
150 a2_prints(apple2_state_t *st, char *s)
151 {
152   while (*s) a2_printc(st, *s++);
153 }
154
155 void
156 a2_goto(apple2_state_t *st, int r, int c)
157 {
158   if (r > 23) r = 23;
159   if (c > 39) r = 39;
160   st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
161   st->cursy=r;
162   st->cursx=c;
163   st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
164 }
165
166 void
167 a2_cls(apple2_state_t *st)
168 {
169   int i;
170   for (i=0; i<24; i++) {
171     memset(st->textlines[i],0xe0,40);
172   }
173 }
174
175 void
176 a2_clear_gr(apple2_state_t *st)
177 {
178   int i;
179   for (i=0; i<24; i++) {
180     memset(st->textlines[i],0x00,40);
181   }
182 }
183
184 void
185 a2_clear_hgr(apple2_state_t *st)
186 {
187   int i;
188   for (i=0; i<192; i++) {
189     memset(st->hireslines[i],0,40);
190   }
191 }
192
193 void
194 a2_invalidate(apple2_state_t *st)
195 {
196 }
197
198 void
199 a2_poke(apple2_state_t *st, int addr, int val)
200 {
201
202   if (addr>=0x400 && addr<0x800) {
203     /* text memory */
204     int row=((addr&0x380)/0x80) + ((addr&0x7f)/0x28)*8;
205     int col=(addr&0x7f)%0x28;
206     if (row<24 && col<40) {
207       st->textlines[row][col]=val;
208       if (!(st->gr_mode&(A2_GR_HIRES)) ||
209           (!(st->gr_mode&(A2_GR_FULL)) && row>=20)) {
210       }
211     }
212   }
213   else if (addr>=0x2000 && addr<0x4000) {
214     int row=(((addr&0x1c00) / 0x400) * 1 +
215              ((addr&0x0380) / 0x80) * 8 +
216              ((addr&0x0078) / 0x28) * 64);
217     int col=((addr&0x07f)%0x28);
218     if (row<192 && col<40) {
219       st->hireslines[row][col]=val;
220       if (st->gr_mode&A2_GR_HIRES) {
221       }
222     }
223   }
224 }
225
226 void
227 a2_hplot(apple2_state_t *st, int hcolor, int x, int y)
228 {
229   int highbit,run;
230
231   highbit=((hcolor<<5)&0x80) ^ 0x80; /* capture bit 2 into bit 7 */
232
233   if (y<0 || y>=192 || x<0 || x>=280) return;
234
235   for (run=0; run<2 && x<280; run++) {
236     u_char *vidbyte = &st->hireslines[y][x/7];
237     u_char whichbit=1<<(x%7);
238     int masked_bit;
239
240     *vidbyte = (*vidbyte & 0x7f) | highbit;
241
242     /* use either bit 0 or 1 of hcolor for odd or even pixels */
243     masked_bit = (hcolor>>(1-(x&1)))&1;
244
245     /* Set whichbit to 1 or 0 depending on color */
246     *vidbyte = (*vidbyte & ~whichbit) | (masked_bit ? whichbit : 0);
247
248     x++;
249   }
250 }
251
252 void
253 a2_hline(apple2_state_t *st, int hcolor, int x1, int y1, int x2, int y2)
254 {
255   int dx,dy,incx,incy,x,y,balance;
256
257   /* Bresenham's line drawing algorithm */
258
259   if (x2>=x1) {
260     dx=x2-x1;
261     incx=1;
262   } else {
263     dx=x1-x2;
264     incx=-1;
265   }
266   if (y2>=y1) {
267     dy=y2-y1;
268     incy=1;
269   } else {
270     dy=y1-y2;
271     incy=-1;
272   }
273
274   x=x1; y=y1;
275
276   if (dx>=dy) {
277     dy*=2;
278     balance=dy-dx;
279     dx*=2;
280     while (x!=x2) {
281       a2_hplot(st, hcolor, x, y);
282       if (balance>=0) {
283         y+=incy;
284         balance-=dx;
285       }
286       balance+=dy;
287       x+=incx;
288     }
289     a2_hplot(st, hcolor, x, y);
290   } else {
291     dx*=2;
292     balance=dx-dy;
293     dy*=2;
294     while (y!=y2) {
295       a2_hplot(st, hcolor, x, y);
296       if (balance>=0) {
297         x+=incx;
298         balance-=dy;
299       }
300       balance+=dx;
301       y+=incy;
302     }
303     a2_hplot(st, hcolor, x, y);
304   }
305 }
306
307 void
308 a2_plot(apple2_state_t *st, int color, int x, int y)
309 {
310   int textrow=y/2;
311   u_char byte;
312
313   if (x<0 || x>=40 || y<0 || y>=48) return;
314
315   byte=st->textlines[textrow][x];
316   if (y&1) {
317     byte = (byte&0xf0) | (color&0x0f);
318   } else {
319     byte = (byte&0x0f) | ((color&0x0f)<<4);
320   }
321   st->textlines[textrow][x]=byte;
322 }
323
324 void
325 a2_display_image_loading(apple2_state_t *st, unsigned char *image,
326                          int lineno)
327 {
328   /*
329     When loading images,it would normally just load the big binary
330     dump into screen memory while you watched. Because of the way
331     screen memory was laid out, it wouldn't load from the top down,
332     but in a funny interleaved way. You should call this with lineno
333     increasing from 0 thru 191 over a period of a few seconds.
334   */
335
336   int row=(((lineno / 24) % 8) * 1 +
337            ((lineno / 3 ) % 8) * 8 +
338            ((lineno / 1 ) % 3) * 64);
339
340   memcpy (st->hireslines[row], &image[row * 40], 40);
341 }
342
343 /*
344   Simulate plausible initial memory contents for running a program.
345 */
346 void
347 a2_init_memory_active(apple2_sim_t *sim)
348 {
349   int i,j,x,y,c;
350   int addr=0;
351   apple2_state_t *st=sim->st;
352
353   while (addr<0x4000) {
354     int n;
355
356     switch (random()%4) {
357     case 0:
358     case 1:
359       n=random()%500;
360       for (i=0; i<n && addr<0x4000; i++) {
361         u_char rb=((random()%6==0 ? 0 : random()%16) |
362                    ((random()%5==0 ? 0 : random()%16)<<4));
363         a2_poke(st, addr++, rb);
364       }
365       break;
366
367     case 2:
368       /* Simulate shapes stored in memory. We use the font since we have it.
369          Unreadable, since rows of each character are stored in consecutive
370          bytes. It was typical to store each of the 7 possible shifts of
371          bitmaps, for fastest blitting to the screen. */
372       x=random()%(sim->text_im->width);
373       for (i=0; i<100; i++) {
374         for (y=0; y<8; y++) {
375           c=0;
376           for (j=0; j<8; j++) {
377             c |= XGetPixel(sim->text_im, (x+j)%sim->text_im->width, y)<<j;
378           }
379           a2_poke(st, addr++, c);
380         }
381         x=(x+1)%(sim->text_im->width);
382       }
383       break;
384
385     case 3:
386       if (addr>0x2000) {
387         n=random()%200;
388         for (i=0; i<n && addr<0x4000; i++) {
389           a2_poke(st, addr++, 0);
390         }
391       }
392       break;
393
394     }
395   }
396 }
397
398
399 /* This table lists fixes for characters that differ from the standard 6x10
400    font. Each encodes a pixel, as (charindex*7 + x) + (y<<10) + (value<<15)
401    where value is 0 for white and 1 for black. */
402 static unsigned short a2_fixfont[] = {
403   /* Fix $ */  0x8421, 0x941d,
404   /* Fix % */  0x8024, 0x0028, 0x8425, 0x0426, 0x0825, 0x1027, 0x1426, 0x9427,
405                0x1824, 0x9828,
406   /* Fix * */  0x8049, 0x8449, 0x8849, 0x0c47, 0x0c48, 0x0c4a, 0x0c4b, 0x9049,
407                0x9449, 0x9849,
408   /* Fix , */  0x9057, 0x1458, 0x9856, 0x1857, 0x1c56,
409   /* Fix . */  0x1465, 0x1864, 0x1866, 0x1c65,
410   /* Fix / */  0x006e, 0x186a,
411   /* Fix 0 */  0x8874, 0x8c73, 0x9072,
412   /* Fix 1 */  0x0878, 0x1878, 0x187c,
413   /* Fix 5 */  0x8895, 0x0c94, 0x0c95,
414   /* Fix 6 */  0x809f, 0x8c9c, 0x109c,
415   /* Fix 7 */  0x8ca4, 0x0ca5, 0x90a3, 0x10a4,
416   /* Fix 9 */  0x08b3, 0x8cb3, 0x98b0,
417   /* Fix : */  0x04b9, 0x08b8, 0x08ba, 0x0cb9, 0x90b9, 0x14b9, 0x18b8, 0x18b9,
418                0x18ba, 0x1cb9,
419   /* Fix ; */  0x04c0, 0x08bf, 0x08c1, 0x0cc0, 0x90c0, 0x14c1, 0x98bf, 0x18c0,
420                0x1cbf,
421   /* Fix < */  0x80c8, 0x00c9, 0x84c7, 0x04c8, 0x88c6, 0x08c7, 0x8cc5, 0x0cc6,
422                0x90c6, 0x10c7,
423                0x94c7, 0x14c8, 0x98c8, 0x18c9,
424   /* Fix > */  0x80d3, 0x00d4, 0x84d4, 0x04d5, 0x88d5, 0x08d6, 0x8cd6, 0x0cd7,
425                0x90d5, 0x10d6,
426                0x94d4, 0x14d5, 0x98d3, 0x18d4,
427   /* Fix @ */  0x88e3, 0x08e4, 0x8ce4, 0x98e5,
428   /* Fix B */  0x84ef, 0x04f0, 0x88ef, 0x08f0, 0x8cef, 0x90ef, 0x10f0, 0x94ef,
429                0x14f0,
430   /* Fix D */  0x84fd, 0x04fe, 0x88fd, 0x08fe, 0x8cfd, 0x0cfe, 0x90fd, 0x10fe,
431                0x94fd, 0x14fe,
432   /* Fix G */  0x8116, 0x0516, 0x9916,
433   /* Fix J */  0x0129, 0x012a, 0x052a, 0x852b, 0x092a, 0x892b, 0x0d2a, 0x8d2b,
434                0x112a, 0x912b,
435                0x152a, 0x952b, 0x992a,
436   /* Fix M */  0x853d, 0x853f, 0x093d, 0x893e, 0x093f,
437   /* Fix Q */  0x915a, 0x155a, 0x955b, 0x155c, 0x195b, 0x995c, 0x1d5c,
438   /* Fix V */  0x8d7b, 0x0d7c, 0x0d7e, 0x8d7f, 0x917b, 0x117c, 0x117e, 0x917f,
439   /* Fix [ */  0x819e, 0x81a2, 0x859e, 0x899e, 0x8d9e, 0x919e, 0x959e, 0x999e,
440                0x99a2,
441   /* Fix \ */  0x01a5, 0x19a9,
442   /* Fix ] */  0x81ac, 0x81b0, 0x85b0, 0x89b0, 0x8db0, 0x91b0, 0x95b0, 0x99ac,
443                0x99b0,
444   /* Fix ^ */  0x01b5, 0x05b4, 0x05b6, 0x09b3, 0x89b5, 0x09b7, 0x8db4, 0x8db6,
445                0x91b3, 0x91b7,
446   /* Fix _ */  0x9db9, 0x9dbf,
447   0,
448 };
449
450 static void
451 a2_make_font(apple2_sim_t *sim)
452 {
453   /*
454     Generate the font. It used a 5x7 font which looks a lot like the standard X
455     6x10 font, with a few differences. So we render up all the uppercase
456     letters of 6x10, and make a few tweaks (like putting a slash across the
457     zero) according to fixfont.
458    */
459
460   int i;
461   const char *def_font="6x10";
462   XFontStruct *font;
463   Pixmap text_pm;
464   GC gc;
465   XGCValues gcv;
466
467   font = XLoadQueryFont (sim->dpy, def_font);
468   if (!font) {
469     fprintf(stderr, "%s: can't load font %s\n", progname, def_font);
470     abort();
471   }
472
473   text_pm=XCreatePixmap(sim->dpy, sim->window, 64*7, 8, sim->dec->xgwa.depth);
474
475   memset(&gcv, 0, sizeof(gcv));
476   gcv.foreground=1;
477   gcv.background=0;
478   gcv.font=font->fid;
479   gc=XCreateGC(sim->dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
480
481   XSetForeground(sim->dpy, gc, 0);
482   XFillRectangle(sim->dpy, text_pm, gc, 0, 0, 64*7, 8);
483   XSetForeground(sim->dpy, gc, 1);
484   for (i=0; i<64; i++) {
485     char c=32+i;
486     int x=7*i+1;
487     int y=7;
488     if (c=='0') {
489       c='O';
490       XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
491     } else {
492       XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
493     }
494   }
495   sim->text_im = XGetImage(sim->dpy, text_pm, 0, 0, 64*7, 8, ~0L, ZPixmap);
496   XFreeGC(sim->dpy, gc);
497   XFreePixmap(sim->dpy, text_pm);
498
499   for (i=0; a2_fixfont[i]; i++) {
500     XPutPixel(sim->text_im, a2_fixfont[i]&0x3ff,
501               (a2_fixfont[i]>>10)&0xf,
502               (a2_fixfont[i]>>15)&1);
503   }
504 }
505
506
507 void
508 apple2(Display *dpy, Window window, int delay,
509        void (*controller)(apple2_sim_t *sim,
510                           int *stepno,
511                           double *next_actiontime))
512 {
513   int i,textrow,row,col,stepno;
514   int c;
515   double next_actiontime;
516   apple2_sim_t *sim;
517
518   sim=(apple2_sim_t *)calloc(1,sizeof(apple2_state_t));
519   sim->dpy = dpy;
520   sim->window = window;
521   sim->delay = delay;
522
523   sim->st = (apple2_state_t *)calloc(1,sizeof(apple2_state_t));
524   sim->dec = analogtv_allocate(dpy, window);
525   sim->dec->event_handler = screenhack_handle_event;
526   sim->inp = analogtv_input_allocate();
527
528   sim->reception.input = sim->inp;
529   sim->reception.level = 1.0;
530
531   sim->prompt=']';
532
533   if (random()%4==0 && !sim->dec->use_cmap && sim->dec->use_color && sim->dec->visbits>=8) {
534     sim->dec->flutter_tint=1;
535   }
536   else if (random()%3==0) {
537     sim->dec->flutter_horiz_desync=1;
538   }
539   sim->typing_rate = 1.0;
540
541   analogtv_set_defaults(sim->dec, "");
542   sim->dec->squish_control=0.05;
543   analogtv_setup_sync(sim->inp, 1, 0);
544
545
546   a2_make_font(sim);
547
548   stepno=0;
549   a2_goto(sim->st,23,0);
550
551   if (random()%2==0) sim->basetime_tv.tv_sec -= 1; /* random blink phase */
552   next_actiontime=0.0;
553
554   sim->curtime=0.0;
555   next_actiontime=sim->curtime;
556   (*controller)(sim, &stepno, &next_actiontime);
557
558 # ifdef GETTIMEOFDAY_TWO_ARGS
559   gettimeofday(&sim->basetime_tv, NULL);
560 # else
561   gettimeofday(&sim->basetime_tv);
562 # endif
563
564   while (1) {
565     double blinkphase;
566
567     {
568       struct timeval curtime_tv;
569 # ifdef GETTIMEOFDAY_TWO_ARGS
570       struct timezone tzp;
571       gettimeofday(&curtime_tv, &tzp);
572 # else
573       gettimeofday(&curtime_tv);
574 # endif
575       sim->curtime=(curtime_tv.tv_sec - sim->basetime_tv.tv_sec) +
576         0.000001*(curtime_tv.tv_usec - sim->basetime_tv.tv_usec);
577       if (sim->curtime > sim->dec->powerup)
578         sim->dec->powerup=sim->curtime;
579     }
580
581     if (analogtv_handle_events(sim->dec)) {
582       sim->typing=NULL;
583       sim->printing=NULL;
584       stepno=A2CONTROLLER_FREE;
585       next_actiontime = sim->curtime;
586       (*controller)(sim, &stepno, &next_actiontime);
587       stepno=0;
588       sim->controller_data=NULL;
589       sim->st->gr_mode=0;
590       continue;
591     }
592
593     blinkphase=sim->curtime/0.8;
594
595     /* The blinking rate was controlled by 555 timer with a resistor/capacitor
596        time constant. Because the capacitor was electrolytic, the flash rate
597        varied somewhat between machines. I'm guessing 1.6 seconds/cycle was
598        reasonable. (I soldered a resistor in mine to make it blink faster.) */
599     i=sim->st->blink;
600     sim->st->blink=((int)blinkphase)&1;
601     if (sim->st->blink!=i && !(sim->st->gr_mode&A2_GR_FULL)) {
602       int downcounter=0;
603       /* For every row with blinking text, set the changed flag. This basically
604          works great except with random screen garbage in text mode, when we
605          end up redrawing the whole screen every second */
606       for (row=(sim->st->gr_mode ? 20 : 0); row<24; row++) {
607         for (col=0; col<40; col++) {
608           c=sim->st->textlines[row][col];
609           if ((c & 0xc0) == 0x40) {
610             downcounter=4;
611             break;
612           }
613         }
614         if (downcounter>0) {
615           downcounter--;
616         }
617       }
618     }
619
620     if (sim->printing) {
621       int nlcnt=0;
622       while (*sim->printing) {
623         if (*sim->printing=='\001') { /* pause */
624           sim->printing++;
625           break;
626         }
627         else if (*sim->printing=='\n') {
628           a2_printc(sim->st,*sim->printing);
629           sim->printing++;
630           nlcnt++;
631           if (nlcnt>=2) break;
632         }
633         else {
634           a2_printc(sim->st,*sim->printing);
635           sim->printing++;
636         }
637       }
638       if (!*sim->printing) sim->printing=NULL;
639     }
640     else if (sim->curtime >= next_actiontime) {
641       if (sim->typing) {
642
643         int c;
644         /* If we're in the midst of typing a string, emit a character with
645            random timing. */
646         c =*sim->typing++;
647         if (c==0) {
648           sim->typing=NULL;
649         }
650         else {
651           a2_printc(sim->st, c);
652           if (c=='\r' || c=='\n') {
653             next_actiontime = sim->curtime;
654           }
655           else if (c==010) {
656             next_actiontime = sim->curtime + 0.1;
657           }
658           else {
659             next_actiontime = (sim->curtime +
660                                (((random()%1000)*0.001 + 0.3) *
661                                 sim->typing_rate));
662           }
663         }
664       } else {
665         next_actiontime=sim->curtime;
666
667         (*controller)(sim, &stepno, &next_actiontime);
668         if (stepno==A2CONTROLLER_DONE) goto finished;
669
670       }
671     }
672
673
674     analogtv_setup_sync(sim->inp, sim->st->gr_mode? 1 : 0, 0);
675     analogtv_setup_frame(sim->dec);
676
677     for (textrow=0; textrow<24; textrow++) {
678       for (row=textrow*8; row<textrow*8+8; row++) {
679
680         /* First we generate the pattern that the video circuitry shifts out
681            of memory. It has a 14.something MHz dot clock, equal to 4 times
682            the color burst frequency. So each group of 4 bits defines a color.
683            Each character position, or byte in hires, defines 14 dots, so odd
684            and even bytes have different color spaces. So, pattern[0..600]
685            gets the dots for one scan line. */
686
687         char *pp=&sim->inp->signal[row+ANALOGTV_TOP+4][ANALOGTV_PIC_START+100];
688
689         if ((sim->st->gr_mode&A2_GR_HIRES) &&
690             (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
691
692           /* Emulate the mysterious pink line, due to a bit getting
693              stuck in a shift register between the end of the last
694              row and the beginning of this one. */
695           if ((sim->st->hireslines[row][0] & 0x80) &&
696               (sim->st->hireslines[row][39]&0x40)) {
697             pp[-1]=ANALOGTV_WHITE_LEVEL;
698           }
699
700           for (col=0; col<40; col++) {
701             u_char b=sim->st->hireslines[row][col];
702             int shift=(b&0x80)?0:1;
703
704             /* Each of the low 7 bits in hires mode corresponded to 2 dot
705                clocks, shifted by one if the high bit was set. */
706             for (i=0; i<7; i++) {
707               pp[shift+1] = pp[shift] = (((b>>i)&1)
708                                          ?ANALOGTV_WHITE_LEVEL
709                                          :ANALOGTV_BLACK_LEVEL);
710               pp+=2;
711             }
712           }
713         }
714         else if ((sim->st->gr_mode&A2_GR_LORES) &&
715                  (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
716           for (col=0; col<40; col++) {
717             u_char nib=((sim->st->textlines[textrow][col] >> (((row/4)&1)*4))
718                         & 0xf);
719             /* The low or high nybble was shifted out one bit at a time. */
720             for (i=0; i<14; i++) {
721               *pp = (((nib>>((col*14+i)&3))&1)
722                      ?ANALOGTV_WHITE_LEVEL
723                      :ANALOGTV_BLACK_LEVEL);
724               pp++;
725             }
726           }
727         }
728         else {
729           for (col=0; col<40; col++) {
730             int rev;
731             c=sim->st->textlines[textrow][col]&0xff;
732             /* hi bits control inverse/blink as follows:
733                0x00: inverse
734                0x40: blink
735                0x80: normal
736                0xc0: normal */
737             rev=!(c&0x80) && (!(c&0x40) || sim->st->blink);
738
739             for (i=0; i<7; i++) {
740               unsigned long pix=XGetPixel(sim->text_im,
741                                           ((c&0x3f)^0x20)*7+i,
742                                           row%8);
743               pp[1] = pp[2] = ((pix^rev)
744                                ?ANALOGTV_WHITE_LEVEL
745                                :ANALOGTV_BLACK_LEVEL);
746               pp+=2;
747             }
748           }
749         }
750       }
751     }
752     analogtv_init_signal(sim->dec, 0.02);
753     analogtv_reception_update(&sim->reception);
754     analogtv_add_signal(sim->dec, &sim->reception);
755     analogtv_draw(sim->dec);
756   }
757
758  finished:
759
760   stepno=A2CONTROLLER_FREE;
761   (*controller)(sim, &stepno, &next_actiontime);
762
763   XSync(sim->dpy, False);
764   XClearWindow(sim->dpy, sim->window);
765 }
766
767 void
768 a2controller_test(apple2_sim_t *sim, int *stepno, double *next_actiontime)
769 {
770   int row,col;
771
772   switch(*stepno) {
773   case 0:
774     a2_invalidate(sim->st);
775     /*
776       For testing color rendering. The spec is:
777       red grn blu
778       0  black       0   0   0
779       1  red       227  30  96
780       2  dk blue    96  78 189
781       3  purple    255  68 253
782       4  dk green    0 163  96
783       5  gray      156 156 156
784       6  med blue   20 207 253
785       7  lt blue   208 195 255
786       8  brown      96 114   3
787       9  orange    255 106  60
788       10 grey      156 156 156
789       11 pink      255 160 208
790       12 lt green   20 245  60
791       13 yellow    208 221 141
792       14 aqua      114 255 208
793       15 white     255 255 255
794     */
795     sim->st->gr_mode=A2_GR_LORES;
796     for (row=0; row<24; row++) {
797       for (col=0; col<40; col++) {
798         sim->st->textlines[row][col]=(row&15)*17;
799       }
800     }
801     *next_actiontime+=0.4;
802     *stepno=99;
803     break;
804
805   case 99:
806     if (sim->curtime > 10) *stepno=-1;
807     break;
808   }
809 }