1 /*
2  * ============================================================================
3  *  Title:    Joystick Support via SDL
4  *  Author:   J. Zbiciak
5  * ============================================================================
6  *  This module implements jzIntv's joystick support.  Specifically, it:
7  *
8  *   -- Enumerates available joysticks
9  *   -- Discovers capabilities of available joysticks
10  *   -- Binds joystick inputs to internal event tags
11  *   -- Allows configuring joystick->event mapping
12  *   -- Decodes analog inputs into Inty's 16-direction input
13  *
14  * ============================================================================
15  */
16 
17 #include "config.h"
18 #include "sdl_jzintv.h"
19 #include "event/event_tbl.h"
20 #include "event/event_plat.h"
21 #include "joy/joy.h"
22 #include "joy/joy_sdl.h"
23 #include "periph/periph.h"
24 #include "cp1600/cp1600.h"
25 #include "cp1600/emu_link.h"
26 
27 
28 LOCAL joy_t *joy     = NULL;
29 LOCAL int    joy_cnt = 0;
30 
31 #define DO_AC       1
32 #define AC_INIT_X   2
33 #define AC_INIT_Y   4
34 #define AC_INIT     (AC_INIT_X + AC_INIT_Y)
35 
36 #define DIR_MAG     (32768)
37 #if defined(PLAT_LINUX)
38 # define PUSH_THRESH (128*DIR_MAG / 6)
39 # define RELS_THRESH (128*DIR_MAG /10)
40 # define AUTOCENTER  (0)
41 #else
42 # define PUSH_THRESH (128*DIR_MAG / 4)
43 # define RELS_THRESH (128*DIR_MAG / 6)
44 # define AUTOCENTER  (DO_AC + AC_INIT)
45 #endif
46 
47 /* convert percentage to threshold and back */
48 #define P2T(x)  ((int)(((x)*128.*DIR_MAG + 50)/ 100.))
49 #define T2P(x)  ((int)(((100. * (x)) + 64.*DIR_MAG)/ (128.*DIR_MAG)))
50 
51 /* convert percentage to direction magnitude and back */
52 #define P2M(x)  ((int)(((x)*DIR_MAG + 50.)/ 100.))
53 #define M2P(x)  ((int)(((100. * (x)) + 0.5*DIR_MAG)/ ((double)DIR_MAG)))
54 
55 /*
56 SDL_NumJoysticks        Count available joysticks.
57 SDL_JoystickName        Get joystick name.
58 SDL_JoystickOpen        Opens a joystick for use.
59 SDL_JoystickIndex       Get the index of an SDL_Joystick.
60 SDL_JoystickNumAxes     Get the number of joystick axes
61 SDL_JoystickNumBalls    Get the number of joystick trackballs
62 SDL_JoystickNumHats     Get the number of joystick hats
63 SDL_JoystickNumButtons  Get the number of joysitck buttons
64 SDL_JoystickUpdate      Updates the state of all joysticks
65 SDL_JoystickGetAxis     Get the current state of an axis
66 SDL_JoystickGetHat      Get the current state of a joystick hat
67 SDL_JoystickGetButton   Get the current state of a given button on a given joystick
68 SDL_JoystickGetBall     Get relative trackball motion
69 SDL_JoystickClose       Closes a previously opened joystick
70 */
71 
72 static int dir_vect[16][2];
73 static const event_num_t joy_dir_map[MAX_JOY][MAX_STICKS] =
74 {
75 #define JOY_DIR_MAP_DECL(n) \
76     {                                                                       \
77         EVENT_JS##n##A_E, EVENT_JS##n##B_E, EVENT_JS##n##C_E,               \
78         EVENT_JS##n##D_E, EVENT_JS##n##E_E, EVENT_JS##n##F_E,               \
79         EVENT_JS##n##G_E, EVENT_JS##n##H_E, EVENT_JS##n##I_E,               \
80         EVENT_JS##n##J_E,                                                   \
81     }
82     JOY_DIR_MAP_DECL(0), JOY_DIR_MAP_DECL(1), JOY_DIR_MAP_DECL(2),
83     JOY_DIR_MAP_DECL(3), JOY_DIR_MAP_DECL(4), JOY_DIR_MAP_DECL(5),
84     JOY_DIR_MAP_DECL(6), JOY_DIR_MAP_DECL(7), JOY_DIR_MAP_DECL(8),
85     JOY_DIR_MAP_DECL(9),
86 };
87 static const event_num_t joy_btn_map[MAX_JOY] =
88 {
89     EVENT_JS0_BTN_00, EVENT_JS1_BTN_00, EVENT_JS2_BTN_00, EVENT_JS3_BTN_00,
90     EVENT_JS4_BTN_00, EVENT_JS5_BTN_00, EVENT_JS6_BTN_00, EVENT_JS7_BTN_00,
91     EVENT_JS8_BTN_00, EVENT_JS9_BTN_00
92 };
93 static const event_num_t joy_hat_map[MAX_JOY] =
94 {
95     EVENT_JS0_HAT0_E, EVENT_JS1_HAT0_E, EVENT_JS2_HAT0_E, EVENT_JS3_HAT0_E,
96     EVENT_JS4_HAT0_E, EVENT_JS5_HAT0_E, EVENT_JS6_HAT0_E, EVENT_JS7_HAT0_E,
97     EVENT_JS8_HAT0_E, EVENT_JS9_HAT0_E
98 };
99 
100 LOCAL int joy_emu_link(cp1600_t *, int *, void *);
101 
102 LOCAL void joy_config(int i, int j, char *cfg);
103 LOCAL void joy_print_config(int j, int s);
104 LOCAL void joy_cleanup_axes(const int js);
105 
106 /* ======================================================================== */
107 /*  JOY_DTOR                                                                */
108 /* ======================================================================== */
joy_dtor(void)109 void joy_dtor(void)
110 {
111     int i;
112 
113     for (i = 0; i < joy_cnt; i++)
114         CONDFREE(joy[i].name);
115 
116     CONDFREE(joy);
117     joy_cnt = 0;
118     memset(dir_vect, 0, sizeof(dir_vect));
119 }
120 
121 /* ======================================================================== */
122 /*  JOY_INIT -- Enumerate available joysticks and features                  */
123 /* ======================================================================== */
joy_init(int verbose,char * cfg[MAX_JOY][MAX_STICKS])124 int joy_init(int verbose, char *cfg[MAX_JOY][MAX_STICKS])
125 {
126     double now = get_time();
127     int jn;     /* Joy number */
128     int sn;     /* Stick number */
129     int an;     /* Axis number */
130 
131     /* -------------------------------------------------------------------- */
132     /*  Initialize the direction vector table.                              */
133     /* -------------------------------------------------------------------- */
134     {
135         int i;
136         for (i = 0; i < 16; i++)
137         {
138             double ang = M_PI * (i / 8.);
139 
140             dir_vect[i][0] = DIR_MAG * cos(ang);
141             dir_vect[i][1] = DIR_MAG *-sin(ang);
142         }
143     }
144 
145     /* -------------------------------------------------------------------- */
146     /*  How many do we have?                                                */
147     /* -------------------------------------------------------------------- */
148     joy_cnt = SDL_NumJoysticks();
149     if (joy_cnt > MAX_JOY)
150         joy_cnt = MAX_JOY;
151 
152     if (!joy_cnt)
153         return 0;
154 
155     if (verbose)
156         jzp_printf("joy:  Found %d joystick(s)\n", joy_cnt);
157 
158     if (!(joy = CALLOC(joy_t, joy_cnt)))
159     {
160         fprintf(stderr, "joy: out of memory\n");
161         exit(1);
162     }
163 
164     /* -------------------------------------------------------------------- */
165     /*  Initialize default behavior.                                        */
166     /* -------------------------------------------------------------------- */
167     for (jn = 0; jn < joy_cnt; jn++)
168     {
169         for (sn = 0; sn < MAX_STICKS; sn++)
170         {
171             joy[jn].stick[sn].push_thresh = PUSH_THRESH;
172             joy[jn].stick[sn].rels_thresh = RELS_THRESH;
173             joy[jn].stick[sn].autocenter  = AUTOCENTER;
174             joy[jn].stick[sn].dir_type    = 1;
175         }
176     }
177 
178     /* -------------------------------------------------------------------- */
179     /*  Ok, what do they look like?                                         */
180     /* -------------------------------------------------------------------- */
181     for (jn = 0; jn < joy_cnt; jn++)
182     {
183         SDL_Joystick *sj;
184         sj = SDL_JoystickOpen(jn);
185         if (sj)
186         {
187 #ifdef USE_SDL2
188             joy[jn].name = strdup(SDL_JoystickName(sj));
189 #else
190             joy[jn].name = strdup(SDL_JoystickName(jn));
191 #endif
192             /* ------------------------------------------------------------ */
193             /*  Get the info available from SDL.                            */
194             /* ------------------------------------------------------------ */
195             joy[jn].ptr         = (void*)sj;
196             joy[jn].num_axes    = SDL_JoystickNumAxes   (sj);
197             joy[jn].num_balls   = SDL_JoystickNumBalls  (sj);
198             joy[jn].num_hats    = SDL_JoystickNumHats   (sj);
199             joy[jn].num_buttons = SDL_JoystickNumButtons(sj);
200 
201             for (an = 0; an < MAX_AXES; an++)
202             {
203                 struct joy_axis_t *const axis = &(joy[jn].axis[an]);
204 
205                 axis->pos = SDL_JoystickGetAxis(sj, an);
206                 axis->ctr = axis->pos;
207 
208                 if (abs(axis->pos) > 1000)
209                     axis->ctr = 0; /* assume accidentally pressed */
210 
211                 axis->prv = axis->ctr;
212                 axis->min = axis->ctr + P2M(-50.0);
213                 axis->max = axis->ctr + P2M(+50.0);
214                 axis->last = now;
215                 axis->stick = -1;  /* unbound, initially */
216             }
217 
218             for (sn = 0; sn < MAX_STICKS; sn++)
219             {
220                 struct joy_stick_t *const stick = &(joy[jn].stick[sn]);
221                 stick->x_axis = -1; /* unbound, initially */
222                 stick->y_axis = -1; /* unbound, initially */
223             }
224 
225             if (verbose)
226             {
227                 jzp_printf("joy:  Joystick JS%d \"%s\"\n", jn, joy[jn].name);
228                 jzp_printf("joy:     %d axes, %d trackball(s), "
229                                 "%d hat(s), %d button(s)\n",
230                         joy[jn].num_axes, joy[jn].num_balls,
231                         joy[jn].num_hats, joy[jn].num_buttons);
232             }
233 
234             /* ------------------------------------------------------------ */
235             /*  Parse configuration strings.  The following four aspects    */
236             /*  can be configured in this way:                              */
237             /*                                                              */
238             /*   -- X/Y axis bindings                                       */
239             /*   -- Autocentering on/off                                    */
240             /*   -- Push/Release thresholds                                 */
241             /*   -- X/Y axis range                                          */
242             /* ------------------------------------------------------------ */
243             for (sn = 0; sn < MAX_STICKS; sn++)
244                 if (cfg[jn][sn])
245                     joy_config(jn, sn, cfg[jn][sn]);
246 
247             joy_cleanup_axes(jn);    /* binds any unbound axes <=> sticks */
248 
249             for (sn = 0; sn < MAX_STICKS; sn++)
250                 if (cfg[jn][sn])
251                     joy_print_config(jn, sn);
252         } else
253         {
254             if (verbose)
255             {
256                 jzp_printf("joy:  Joystick JS%d \"%s\"\n", jn, joy[jn].name);
257                 jzp_printf("joy:     Unavailable:  Could not open.\n");
258             }
259         }
260     }
261 
262     /* -------------------------------------------------------------------- */
263     /*  Register our EMU_LINK API.  I'll put this on API #8.                */
264     /* -------------------------------------------------------------------- */
265     emu_link_register(joy_emu_link, 8, NULL);
266 
267     return 0;
268 }
269 
270 /* ======================================================================== */
271 /*  JOY_CLEANUP_AXES                                                        */
272 /* ======================================================================== */
joy_cleanup_axes(const int jn)273 LOCAL void joy_cleanup_axes(const int jn)
274 {
275     int sn, an;
276     int axis_stick[MAX_AXES];
277 
278     /* -------------------------------------------------------------------- */
279     /*  Assume all axes are not bound to sticks.                            */
280     /* -------------------------------------------------------------------- */
281     for (an = 0; an < MAX_AXES; an++)
282         axis_stick[an] = -1;
283 
284     /* -------------------------------------------------------------------- */
285     /*  Step through all sticks, and mark their axes as bound to them.      */
286     /* -------------------------------------------------------------------- */
287     for (sn = 0; sn < MAX_STICKS; sn++)
288     {
289         struct joy_stick_t *stick = &joy[jn].stick[sn];
290         if (stick->x_axis >= 0) axis_stick[stick->x_axis] = sn;
291         if (stick->y_axis >= 0 && !IS_DUMMY_AXIS(stick->y_axis))
292             axis_stick[stick->y_axis] = sn;
293     }
294 
295     /* -------------------------------------------------------------------- */
296     /*  Fill in any unmapped Stick => Axis bindings.                        */
297     /*  As long as MAX_AXES >= MAX_STICKS * 2, this always converges.       */
298     /* -------------------------------------------------------------------- */
299     for (sn = 0, an = 0; sn < MAX_STICKS; sn++)
300     {
301         struct joy_stick_t *stick = &joy[jn].stick[sn];
302         if (stick->x_axis < 0)
303         {
304             while (an < MAX_AXES - 1 && axis_stick[an] >= 0)
305                 an++;
306             stick->x_axis = an;
307             axis_stick[an] = sn;
308         }
309         if (stick->y_axis < 0)
310         {
311             while (an < MAX_AXES - 1 && axis_stick[an] >= 0)
312                 an++;
313             stick->y_axis = an;
314             axis_stick[an] = sn;
315         }
316     }
317 
318     /* -------------------------------------------------------------------- */
319     /*  Now map joy[jn].axis[an] back to the corresponding stick.           */
320     /* -------------------------------------------------------------------- */
321     for (an = 0; an < MAX_AXES; an++)
322         joy[jn].axis[an].stick = axis_stick[an];
323 }
324 
325 /* ======================================================================== */
326 /*  JOY_CONFIG -- Parse configuration string for a joystick.                */
327 /* ======================================================================== */
joy_config(int i,int st,char * cfg)328 LOCAL void joy_config(int i, int st, char *cfg)
329 {
330     char *s1, *s2;
331     int v, j;
332     int lo, hi;
333     char *tmp = strdup(cfg);
334     char *mem = tmp;
335 
336     /* -------------------------------------------------------------------- */
337     /*  Strip off any quotes that get to us.                                */
338     /* -------------------------------------------------------------------- */
339     if (tmp[0] == '"' || tmp[0] == '\'')
340         tmp++;
341 
342     if ((s1 = strrchr(tmp, '"' )) != NULL ||
343         (s1 = strrchr(tmp, '\'')) != NULL)
344         *s1 = 0;
345 
346     /* -------------------------------------------------------------------- */
347     /*  Pull off comma-separated sections and parse them.                   */
348     /* -------------------------------------------------------------------- */
349     s1 = strtok(tmp, ",");
350 
351     while (s1)
352     {
353         s2 = s1;
354         while (*s2 && *s2 != '=')
355             s2++;
356 
357         if (*s2)
358             *s2++ = '\0';
359 
360         v = atoi(s2);
361 
362         struct joy_stick_t *stick = &joy[i].stick[st];
363 
364         if      (!strcmp(s1, "ac")           ) stick->autocenter = AC_INIT;
365         else if (!strcmp(s1, "noac")         ) stick->autocenter = 0;
366         else if (!strcmp(s1, "push")   && *s2) stick->push_thresh=P2T(atoi(s2));
367         else if (!strcmp(s1, "rels")   && *s2) stick->rels_thresh=P2T(atoi(s2));
368         else if (!strcmp(s1, "button")       ) stick->dir_type = 8;
369         else if (!strcmp(s1, "4diag")        ) stick->dir_type = -4;
370         else if (!strcmp(s1, "4dir")         ) stick->dir_type = 4;
371         else if (!strcmp(s1, "8dir")         ) stick->dir_type = 2;
372         else if (!strcmp(s1, "16dir")        ) stick->dir_type = 1;
373         else if ((!strcmp(s1, "xaxis" ) || !strcmp(s1, "yaxis")) && *s2)
374         {
375             if (v >= MAX_AXES)
376             {
377                 fprintf(stderr,
378                         "joy:  JS%d%c: axis %d out of range (max %d)\n",
379                         i, 'A'+st, v, MAX_AXES);
380                 exit(1);
381             }
382             /* Go unmap any conflicts */
383             for (j = 0; j < MAX_STICKS; j++)
384             {
385                 if (joy[i].stick[j].x_axis == v) joy[i].stick[j].x_axis = -1;
386                 if (joy[i].stick[j].y_axis == v) joy[i].stick[j].y_axis = -1;
387             }
388 
389             /* Stick => Axis map */
390             if (s1[0] == 'x') stick->x_axis = v;
391             else              stick->y_axis = v;
392 
393             /* Axis => Stick map */
394             joy[i].axis[v].stick = st;
395         }
396         else if ((!strcmp(s1, "xrng") || !strcmp(s1, "yrng")) &&
397                  *s2 && 2 == sscanf(s2, "%d:%d", &lo, &hi))
398         {
399             int ax = s1[0] == 'x' ? stick->x_axis : stick->y_axis;
400 
401             joy[i].axis[ax].min = P2M(lo);
402             joy[i].axis[ax].max = P2M(hi);
403         } else
404         {
405             fprintf(stderr, "joy:  Unknown joystick config key '%s'!\n", s1);
406             exit(1);
407         }
408         s1 = strtok(NULL, ",");
409     }
410 
411     /* -------------------------------------------------------------------- */
412     /*  For "button" axes, force y_axis to DUMMY_AXIS.                      */
413     /* -------------------------------------------------------------------- */
414     for (j = 0; j < MAX_STICKS; j++)
415     {
416         if (joy[i].stick[j].dir_type == 8)
417             joy[i].stick[j].y_axis = DUMMY_AXIS;
418     }
419 
420     /* -------------------------------------------------------------------- */
421     /*  Sanity check push/release                                           */
422     /* -------------------------------------------------------------------- */
423     for (j = 0; j < MAX_STICKS; j++)
424     {
425         if (joy[i].stick[j].push_thresh < joy[i].stick[j].rels_thresh)
426         {
427             joy[i].stick[j].push_thresh = joy[i].stick[j].rels_thresh;
428             jzp_printf("joy:    "
429                    "Warning: Push threshold below release on JS%d%c.  "
430                    "Setting push = release.\n", i, 'A'+j);
431         }
432     }
433 
434     /* -------------------------------------------------------------------- */
435     /*  Invert any axes for which inversion was requested.                  */
436     /* -------------------------------------------------------------------- */
437     for (j = 0; j < MAX_AXES; j++)
438     {
439         if (joy[i].axis[j].min > joy[i].axis[j].max)
440         {
441             int t;
442             jzp_printf("joy:    Inverting axis %d because max < min\n",
443                     j);
444 
445             t = joy[i].axis[j].min;
446             joy[i].axis[j].min = joy[i].axis[j].max;
447             joy[i].axis[j].max = t;
448             joy[i].axis[j].inv = 1;
449         }
450     }
451 
452     free(mem);
453 }
454 
455 /* ======================================================================== */
456 /*  JOY_GET_AXIS                                                            */
457 /* ======================================================================== */
joy_get_axis(const int j,const int axis)458 LOCAL struct joy_axis_t *joy_get_axis(const int j, const int axis)
459 {
460     if (IS_DUMMY_AXIS(axis))
461         return &joy[j].axis[MAX_AXES];
462     else
463         return &joy[j].axis[axis];
464 }
465 
466 /* ======================================================================== */
467 /*  JOY_PRINT_CONFIG                                                        */
468 /* ======================================================================== */
joy_print_config(int j,int s)469 LOCAL void joy_print_config(int j, int s)
470 {
471     /* -------------------------------------------------------------------- */
472     /*  Summarize resulting configuration.                                  */
473     /* -------------------------------------------------------------------- */
474     struct joy_stick_t *stick  = &joy[j].stick[s];
475     struct joy_axis_t  *x_axis = joy_get_axis(j, stick->x_axis);
476     struct joy_axis_t  *y_axis = joy_get_axis(j, stick->y_axis);
477 
478     if (stick->dir_type != 8)
479     {
480         jzp_printf(
481 "joy:     JS%d%c: X-Axis = axis %d  Y-Axis = axis %d  Autocenter = %-3s\n"
482 "joy:           Push threshold = %d%%  Release threshold = %d%%\n"
483 "joy:           X range = [%d%%,%d%%]  Y range = [%d%%,%d%%]\n"
484 "joy:           Directions = %d%s\n"
485 "--js%d%c=\"xaxis=%d,yaxis=%d,%sac,push=%d,rels=%d,xrng=%d:%d,yrng=%d:%d,%s\"\n",
486             j, 'A' + s,
487             stick->x_axis, stick->y_axis,
488             stick->autocenter ? "ON" : "off",
489             T2P(stick->push_thresh), T2P(stick->rels_thresh),
490             M2P(x_axis->min), M2P(x_axis->max),
491             M2P(y_axis->min), M2P(y_axis->max),
492             16 / abs(stick->dir_type),
493             stick->dir_type <  0 ? " diagonal bias" : "",
494             j, 'a' + s, stick->x_axis, stick->y_axis,
495             stick->autocenter ? "" : "no",
496             T2P(stick->push_thresh), T2P(stick->rels_thresh),
497             M2P(x_axis->min), M2P(x_axis->max),
498             M2P(y_axis->min), M2P(y_axis->max),
499             stick->dir_type ==-4 ? "4diag":
500             stick->dir_type == 4 ? "4dir" :
501             stick->dir_type == 2 ? "8dir" : "16dir");
502     } else
503     {
504         /* "Button" mode omits yaxis info, as it is irrelevant. */
505         jzp_printf(
506 "joy:     JS%d%c: X-Axis = axis %d  Y-Axis = None  Autocenter = %-3s\n"
507 "joy:           Push threshold = %d%%  Release threshold = %d%%\n"
508 "joy:           X range = [%d%%,%d%%]  Y range = N/A\n"
509 "joy:           Directions = 2\n"
510 "--js%d%c=\"xaxis=%d,%sac,push=%d,rels=%d,xrng=%d:%d,button\"\n",
511             j, 'A' + s, stick->x_axis, stick->autocenter ? "ON" : "off",
512             T2P(stick->push_thresh), T2P(stick->rels_thresh),
513             M2P(x_axis->min), M2P(x_axis->max),
514             j, 'a' + s, stick->x_axis,
515             stick->autocenter ? "" : "no",
516             T2P(stick->push_thresh), T2P(stick->rels_thresh),
517             M2P(x_axis->min), M2P(x_axis->max));
518     }
519 }
520 
521 /* ======================================================================== */
522 /*  JOY_NORMALIZE_AXIS                                                      */
523 /* ======================================================================== */
joy_normalize_axis(struct joy_axis_t * axis)524 LOCAL int joy_normalize_axis(struct joy_axis_t *axis)
525 {
526     int pos, ctr, min, max;
527 
528     /* -------------------------------------------------------------------- */
529     /*  Linearly interpolate the swing from ctr to edge to the range 0-128  */
530     /* -------------------------------------------------------------------- */
531     pos = axis->pos;
532     ctr = axis->ctr;
533     min = axis->min;
534     max = axis->max;
535     if (min == ctr) min--;
536     if (max == ctr) max++;
537 
538     if (pos > ctr)  return   ((pos - ctr) * 128) / (max - ctr);
539     else            return - ((ctr - pos) * 128) / (ctr - min);
540 }
541 
542 
543 /* ======================================================================== */
544 /*  JOY_DECODE_AXIS                                                         */
545 /* ======================================================================== */
joy_decode_axis(const SDL_Event * const ev,event_updn_t * const ev_updn,event_num_t * const ev_num)546 LOCAL void joy_decode_axis
547 (
548     const SDL_Event *const ev,
549     event_updn_t    *const ev_updn,
550     event_num_t     *const ev_num
551 )
552 {
553     int a = ev->jaxis.axis;
554     int i = ev->jaxis.which;
555     int v = ev->jaxis.value;
556     int u, f;
557     int norm_x, norm_y;
558     int j, dotp, best, best_dotp = 0;
559     int ox, oy;
560     struct joy_axis_t *axis, *x_axis, *y_axis;
561     struct joy_stick_t *stick;
562     int st;
563 
564     /* -------------------------------------------------------------------- */
565     /*  Ignore axes and joysticks we can't track.                           */
566     /* -------------------------------------------------------------------- */
567     if (ev->jaxis.which >= joy_cnt || ev->jaxis.axis >= MAX_AXES)
568     {
569         ev_num[0] = EVENT_IGNORE;
570 #ifdef JOY_DEBUG
571         printf("\rDROP axis event: %d %d      \n",
572                ev->jaxis.which, ev->jaxis.axis);
573         fflush(stdout);
574 #endif
575         return;
576     }
577 
578     axis = &joy[i].axis[a];
579 
580     /* -------------------------------------------------------------------- */
581     /*  Update autoranging.                                                 */
582     /* -------------------------------------------------------------------- */
583     if (axis->inv)     v = -v;
584     if (axis->min > v) axis->min = v;
585     if (axis->max < v) axis->max = v;
586     axis->pos = v;
587 
588     /* -------------------------------------------------------------------- */
589     /*  The remaining code is specific to X/Y axis updates.                 */
590     /* -------------------------------------------------------------------- */
591     if (axis->stick < 0 || axis->stick >= MAX_STICKS)
592     {
593 #ifdef JOY_DEBUG
594         printf("\rDROP axis event: %d %d (not X/Y axis)      \n",
595                ev->jaxis.which, ev->jaxis.axis);
596         fflush(stdout);
597 #endif
598         ev_num[0] = EVENT_IGNORE;
599         return;
600     }
601 
602     /* Map back to this axis' stick */
603     st = axis->stick;
604     stick = &joy[i].stick[axis->stick];
605 
606     /* Get both axes associated with the stick */
607     x_axis = joy_get_axis(i, stick->x_axis);
608     y_axis = joy_get_axis(i, stick->y_axis);
609 
610     /* -------------------------------------------------------------------- */
611     /*  Update autocentering.  Do a decaying average of all samples that    */
612     /*  are less than 1/8th the current release-threshold away from what    */
613     /*  we consider as the current center.  An enterprising gamer might     */
614     /*  fool this algorithm by rocking the joystick just off center. BFD.   */
615     /*                                                                      */
616     /*  The autoweighting function actually looks at the previous sample    */
617     /*  and how long it was held for.  Samples that were held longer are    */
618     /*  more likely to be "center."  The adaptation is simple:  Assume      */
619     /*  a desired sample rate and corresponding exponent.  For samples      */
620     /*  held longer, convert the held sample into multiple equal-valued     */
621     /*  samples.                                                            */
622     /*                                                                      */
623     /*  In this case, the exponent is 1/64, and the assumed sample rate     */
624     /*  is 32Hz.  (Powers of two, ya know.)  If more than 1 sec elapsed     */
625     /*  we clamp at 1 sec.                                                  */
626     /* -------------------------------------------------------------------- */
627     f = axis == x_axis ? AC_INIT_X : AC_INIT_Y;
628 
629     /* ugh, gotta get the float out of this someday. */
630     ox = x_axis->prv - x_axis->ctr;
631     oy = y_axis->prv - y_axis->ctr;
632     dotp = ((double)ox*128./SHRT_MAX) * ((double)ox*DIR_MAG/SHRT_MAX) +
633            ((double)oy*128./SHRT_MAX) * ((double)oy*DIR_MAG/SHRT_MAX);
634 
635 /*  jzp_printf("dotp=%d rt=%d\n",dotp*8, stick->rels_thresh); */
636 
637     if ((stick->autocenter & f) == f && v != 0)
638     {
639         /* ---------------------------------------------------------------- */
640         /*  If it's a digital joystick, it'll snap to a dir.  Otherwise,    */
641         /*  if it's a slow motion, use it as initial centering estimate.    */
642         /* ---------------------------------------------------------------- */
643         if (abs(v) * 8 < SHRT_MAX) axis->ctr = v;
644         else                       axis->ctr = 0;
645 
646         stick->autocenter &= ~f;
647     } else
648     if ((stick->autocenter & DO_AC) && dotp*8 < stick->rels_thresh &&
649         abs(v) * 8 < SHRT_MAX)
650     {
651         double now  = get_time();
652         double then = axis->last;
653         int iters;
654 
655         u = axis->prv;
656 
657         iters = 32 * (now - then) + 0.5;
658         if (iters > 32) iters = 32;
659         if (iters <= 0) iters = 1;
660 
661 /*jzp_printf("iters=%d axis=%d then=%f now=%f\n", iters, a, then*16, now*16);*/
662         while (iters-- > 0)
663             axis->ctr = (axis->ctr * 63 + 31 + u) >> 6;
664 
665         axis->last = now;
666     }
667 
668     axis->prv = v;
669 
670     /* -------------------------------------------------------------------- */
671     /*  Ok, if this was either the X or Y axis, determine if we generate a  */
672     /*  DISC event.                                                         */
673     /*                                                                      */
674     /*  Decoding strategy:                                                  */
675     /*                                                                      */
676     /*   -- Normalize the input to a +/- 128 range based on our autocenter  */
677     /*      and autoranging.                                                */
678     /*                                                                      */
679     /*   -- Decide whether disc is up or down based on hysteresis.          */
680     /*                                                                      */
681     /*       -- The disc gets pressed when the joystick is more than        */
682     /*          1/4 of the way to the edge from the center along its        */
683     /*          closest direction line.                                     */
684     /*                                                                      */
685     /*       -- The disc gets released when the joystick is less than       */
686     /*          1/6 along its closest direction line.                       */
687     /*                                                                      */
688     /*   -- If the disc is pressed, decode the direction into one of 16.    */
689     /*      Rather than do trig with arctan and all that jazz, I instead    */
690     /*      take the dot product of the joystick position with 16           */
691     /*      different normalized direction vectors, and return the largest  */
692     /*      as the best match.  Yay vector algebra.                         */
693     /*                                                                      */
694     /* -------------------------------------------------------------------- */
695 
696     /* -------------------------------------------------------------------- */
697     /*  Normalize the X/Y.  This returns stuff in a +/- 128 range.          */
698     /* -------------------------------------------------------------------- */
699     norm_x = joy_normalize_axis(x_axis);
700     norm_y = joy_normalize_axis(y_axis);
701 
702     /* -------------------------------------------------------------------- */
703     /*  Figure out which of the 16 directions is closest to the dir we're   */
704     /*  pointing.  We apply the "press" and "release" thresholds to the     */
705     /*  dot product we calculate below.                                     */
706     /* -------------------------------------------------------------------- */
707     best = stick->disc_dir;
708     for (j = stick->dir_type < 0 ? 2 : 0; j < 16; j += abs(stick->dir_type))
709     {
710         dotp = dir_vect[j][0] * norm_x + dir_vect[j][1] * norm_y;
711         if (best_dotp < dotp)
712         {
713             best_dotp = dotp;
714             best = j;
715         }
716     }
717 
718     ev_updn[0] = stick->disc_dir == -1 ? EV_UP : EV_DOWN;
719 
720     if (best_dotp < stick->rels_thresh && stick->disc_dir != -1)
721     {
722         ev_updn[0] = EV_UP;
723         ev_num[0]  = EVENT_NUM_OFS(joy_dir_map[i][st], stick->disc_dir);
724         stick->disc_dir = -1;
725         return;
726     }
727 
728     if (best_dotp <= stick->push_thresh && stick->disc_dir == -1)
729     {
730         ev_updn[0] = EV_UP;
731         ev_num[0]  = EVENT_IGNORE;
732         return;
733     }
734 
735     if (((best_dotp > stick->push_thresh && stick->disc_dir == -1) ||
736          (best_dotp > stick->rels_thresh && stick->disc_dir != best)) &&
737          best != -1)
738     {
739         ev_updn[0] = EV_DOWN;
740         ev_num[0]  = EVENT_NUM_OFS(joy_dir_map[i][st], best);
741         stick->disc_dir = best;
742         return;
743     }
744 
745     ev_num[0] = EVENT_IGNORE;
746 #ifdef JOY_DEBUG
747     printf("\rDROP axis event: %d %d (reached end of decode axis)    \n",
748            ev->jaxis.which, ev->jaxis.axis);
749     fflush(stdout);
750 #endif
751     return;
752 }
753 
754 /* ======================================================================== */
755 /*  JOY_DECODE_HAT                                                          */
756 /* ======================================================================== */
joy_decode_hat(const SDL_Event * const ev,event_updn_t * const ev_updn,event_num_t * const ev_num)757 LOCAL void joy_decode_hat
758 (
759     const SDL_Event *const ev,
760     event_updn_t    *const ev_updn,
761     event_num_t     *const ev_num
762 )
763 {
764     if (ev->jhat.which >= joy_cnt || ev->jhat.hat >= MAX_HATS)
765     {
766         ev_num[0] = EVENT_IGNORE;
767 #ifdef JOY_DEBUG
768         printf("\rDROP hat event: %d %d      \n",
769                ev->jhat.which, ev->jhat.hat);
770         fflush(stdout);
771 #endif
772         return;
773     }
774 
775     const event_num_t base = joy_hat_map[ev->jhat.which] + 8*ev->jhat.hat;
776     ev_updn[0] = EV_DOWN;
777 
778     /* Offsets to the cardinal directions */
779     enum { E = 0, NE, N, NW, W, SW, S, SE };
780 
781     if (ev->jhat.value != joy[ev->jhat.which].hat_dir[ev->jhat.hat])
782     {
783         ev_updn[1] = EV_UP;
784         switch (joy[ev->jhat.which].hat_dir[ev->jhat.hat])
785         {
786             case SDL_HAT_RIGHT:     ev_num[1] = EVENT_NUM_OFS(base, E ); break;
787             case SDL_HAT_RIGHTUP:   ev_num[1] = EVENT_NUM_OFS(base, NE); break;
788             case SDL_HAT_UP:        ev_num[1] = EVENT_NUM_OFS(base, N ); break;
789             case SDL_HAT_LEFTUP:    ev_num[1] = EVENT_NUM_OFS(base, NW); break;
790             case SDL_HAT_LEFT:      ev_num[1] = EVENT_NUM_OFS(base, W ); break;
791             case SDL_HAT_LEFTDOWN:  ev_num[1] = EVENT_NUM_OFS(base, SW); break;
792             case SDL_HAT_DOWN:      ev_num[1] = EVENT_NUM_OFS(base, S ); break;
793             case SDL_HAT_RIGHTDOWN: ev_num[1] = EVENT_NUM_OFS(base, SE); break;
794             case SDL_HAT_CENTERED:  ev_num[1] = EVENT_IGNORE; break;
795             default:
796                ev_num[1] = EVENT_IGNORE;
797                jzp_printf("Warning: Unknown hat input %d\n", ev->jhat.value);
798         }
799     }
800 
801     switch (ev->jhat.value)
802     {
803         case SDL_HAT_RIGHT:     ev_num[0] = EVENT_NUM_OFS(base, E ); break;
804         case SDL_HAT_RIGHTUP:   ev_num[0] = EVENT_NUM_OFS(base, NE); break;
805         case SDL_HAT_UP:        ev_num[0] = EVENT_NUM_OFS(base, N ); break;
806         case SDL_HAT_LEFTUP:    ev_num[0] = EVENT_NUM_OFS(base, NW); break;
807         case SDL_HAT_LEFT:      ev_num[0] = EVENT_NUM_OFS(base, W ); break;
808         case SDL_HAT_LEFTDOWN:  ev_num[0] = EVENT_NUM_OFS(base, SW); break;
809         case SDL_HAT_DOWN:      ev_num[0] = EVENT_NUM_OFS(base, S ); break;
810         case SDL_HAT_RIGHTDOWN: ev_num[0] = EVENT_NUM_OFS(base, SE); break;
811         case SDL_HAT_CENTERED:  ev_num[0] = EVENT_IGNORE; break;
812         default:
813             ev_num[0] = EVENT_IGNORE;
814             jzp_printf("Warning: Unknown hat input %d\n", ev->jhat.value);
815     }
816 
817     joy[ev->jhat.which].hat_dir[ev->jhat.hat] = ev->jhat.value;
818 
819     return;
820 }
821 
822 /* ======================================================================== */
823 /*  JOY_DECODE_BUTTON                                                       */
824 /* ======================================================================== */
joy_decode_button(const SDL_Event * const ev,event_num_t * const ev_num)825 LOCAL void joy_decode_button(const SDL_Event *const ev,
826                              event_num_t *const ev_num)
827 {
828     if (ev->jbutton.which >= joy_cnt || ev->jbutton.button > 31)
829     {
830 #ifdef JOY_DEBUG
831         printf("\rDROP button event: %d %d      \n",
832                ev->jbutton.which, ev->jbutton.button);
833         fflush(stdout);
834 #endif
835         *ev_num = EVENT_IGNORE;
836         return;
837     }
838 
839     *ev_num = EVENT_NUM_OFS(joy_btn_map[ev->jbutton.which], ev->jbutton.button);
840     return;
841 }
842 
843 
844 
845 /* ======================================================================== */
846 /*  JOY_DECODE_EVENT -- Pull apart an SDL_EVENT and turn it into our        */
847 /*                      internal event numbers.                             */
848 /* ======================================================================== */
joy_decode_event(const SDL_Event * const ev,event_updn_t * const ev_updn,event_num_t * const ev_num)849 bool joy_decode_event
850 (
851     const SDL_Event *const ev,
852     event_updn_t    *const ev_updn,
853     event_num_t     *const ev_num
854 )
855 {
856     bool may_combo = false;
857 
858     switch (ev->type)
859     {
860         case SDL_JOYAXISMOTION:
861         {
862             joy_decode_axis(ev, ev_updn, ev_num);
863             break;
864         }
865         case SDL_JOYHATMOTION:
866         {
867             joy_decode_hat(ev, ev_updn, ev_num);
868             break;
869         }
870 
871         case SDL_JOYBUTTONDOWN:
872         case SDL_JOYBUTTONUP:
873         {
874             may_combo = true;
875             *ev_updn = ev->type == SDL_JOYBUTTONDOWN ? EV_DOWN : EV_UP;
876             joy_decode_button(ev, ev_num);
877             break;
878         }
879 
880         case SDL_JOYBALLMOTION:
881         {
882             /* ignored */
883 #ifdef JOY_DEBUG
884             printf("\rDROP ball event: %d %d [%4d,%4d]     \n",
885                    ev->jball.which, ev->jball.ball,
886                    ev->jball.xrel, ev->jball.yrel);
887             fflush(stdout);
888 #endif
889 
890             *ev_num = EVENT_IGNORE;
891             break;
892         }
893 
894         default: *ev_num = EVENT_IGNORE; break;
895     }
896 
897     if (ev_num[0] == EVENT_IGNORE || ev_num[1] != EVENT_IGNORE)
898         may_combo = false;
899 
900     return may_combo;
901 }
902 
903 /* ======================================================================== */
904 /*  JOY_EMU_LINK -- Allow programs to get analog joystick info.             */
905 /* ======================================================================== */
joy_emu_link(cp1600_t * cpu,int * fail,void * opaque)906 LOCAL int joy_emu_link(cp1600_t *cpu, int *fail, void *opaque)
907 {
908     int js, st;
909     int api;
910     struct joy_t *joyp;
911     struct joy_stick_t *stick;
912     struct joy_axis_t *x_axis, *y_axis;
913 
914     UNUSED(opaque);
915 
916     /* -------------------------------------------------------------------- */
917     /*  Sub-APIs we export:                                                 */
918     /*                                                                      */
919     /*    R2:  Sub-API number (table below)                                 */
920     /*    R3:  LSB is Joy #, MSB is Stick #                                 */
921     /*                                                                      */
922     /*  00: Number of joysticks.  Result in R0.  Ignores R3.                */
923     /*  01: Get geometry: Returns # of axes, balls, hats, buttons in R0..R3 */
924     /*  02: Get X/Y raw pos:  Returns 16-bit X/Y pos in R1, R2.             */
925     /*  03: Get X/Y raw min:  Returns 16-bit X/Y min in R1, R2.             */
926     /*  04: Get X/Y raw max:  Returns 16-bit X/Y max in R1, R2.             */
927     /*  05: Get X/Y raw ctr:  Returns 16-bit X/Y max in R1, R2.             */
928     /*  06: Get X/Y cooked:   Norm'd 8-bit X/Y in R1, R2. Disc Dir in R0.   */
929     /*  07: Get buttons.  Returns 32-bit bitmap in R1, R2.                  */
930     /*  08: Get hats.  Returns hats 0..3 in 4 x 4-bit fields in R0.         */
931     /* -------------------------------------------------------------------- */
932     if (cpu->r[2] == 0x00)
933     {
934         *fail = 0;
935         return joy_cnt;
936     }
937 
938     js = cpu->r[3] & 0xFF;
939     st = (cpu->r[3] >> 8) & 0xFF;
940     api = cpu->r[2];
941 
942     if (api > 0x08 || js >= joy_cnt || st >= MAX_STICKS)
943     {
944         *fail = 1;
945         return 0xFFFF;
946     }
947 
948     joyp = joy + js;
949     stick = &joyp->stick[st];
950     x_axis = joy_get_axis(js, stick->x_axis);
951     y_axis = joy_get_axis(js, stick->y_axis);
952 
953     switch (api)
954     {
955         case 0x01:
956         {
957             *fail = 0;
958             cpu->r[1] = joyp->num_balls;
959             cpu->r[2] = joyp->num_hats;
960             cpu->r[3] = joyp->num_buttons;
961             return      joyp->num_axes;
962         }
963 
964         case 0x02:
965         {
966             *fail = 0;
967             cpu->r[1] = x_axis->pos;
968             cpu->r[2] = y_axis->pos;
969             return 0;
970         }
971 
972         case 0x03:
973         {
974             *fail = 0;
975             cpu->r[1] = x_axis->min;
976             cpu->r[2] = y_axis->min;
977             return 0;
978         }
979 
980         case 0x04:
981         {
982             *fail = 0;
983             cpu->r[1] = x_axis->max;
984             cpu->r[2] = y_axis->max;
985             return 0;
986         }
987 
988         case 0x05:
989         {
990             *fail = 0;
991             cpu->r[1] = x_axis->ctr;
992             cpu->r[2] = y_axis->ctr;
993             return 0;
994         }
995 
996         case 0x06:
997         {
998             *fail = 0;
999             cpu->r[1] = joy_normalize_axis(x_axis);
1000             cpu->r[2] = joy_normalize_axis(y_axis);
1001             return stick->disc_dir;
1002         }
1003 
1004         case 0x07:
1005         {
1006             uint32_t buttons = 0;
1007             int i;
1008 
1009             *fail = 0;
1010             for (i = 0; i < joyp->num_buttons; i++)
1011                 buttons |= (SDL_JoystickGetButton(
1012                                 (SDL_Joystick *)joyp->ptr,i) != 0) << i;
1013 
1014             cpu->r[1] =  buttons        & 0xFFFF;
1015             cpu->r[2] = (buttons >> 16) & 0xFFFF;
1016 
1017             return 0;
1018         }
1019 
1020         case 0x08:
1021         {
1022             uint32_t hats = 0;
1023             int i;
1024 
1025             *fail = 0;
1026             for (i = 0; i < joyp->num_hats; i++)
1027             {
1028                 int p;
1029 
1030                 switch (joyp->hat_dir[i])
1031                 {
1032                     case SDL_HAT_RIGHT:         p = 0;  break; /* E    */
1033                     case SDL_HAT_RIGHTUP:       p = 1;  break; /* NE   */
1034                     case SDL_HAT_UP:            p = 2;  break; /* N    */
1035                     case SDL_HAT_LEFTUP:        p = 3;  break; /* NW   */
1036                     case SDL_HAT_LEFT:          p = 4;  break; /* W    */
1037                     case SDL_HAT_LEFTDOWN:      p = 5;  break; /* SW   */
1038                     case SDL_HAT_DOWN:          p = 6;  break; /* S    */
1039                     case SDL_HAT_RIGHTDOWN:     p = 7;  break; /* SE   */
1040                     case SDL_HAT_CENTERED:      p = 15; break; /* center */
1041                     default:                    p = 15; break;
1042                 }
1043                 hats |= p << (4 * i);
1044             }
1045 
1046             return hats & 0xFFFF;
1047         }
1048 
1049         default:
1050             break;
1051     }
1052 
1053     *fail = 1;
1054     return 0xFFFF;
1055 }
1056 
1057 /* ======================================================================== */
1058 /*  This program is free software; you can redistribute it and/or modify    */
1059 /*  it under the terms of the GNU General Public License as published by    */
1060 /*  the Free Software Foundation; either version 2 of the License, or       */
1061 /*  (at your option) any later version.                                     */
1062 /*                                                                          */
1063 /*  This program is distributed in the hope that it will be useful,         */
1064 /*  but WITHOUT ANY WARRANTY; without even the implied warranty of          */
1065 /*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       */
1066 /*  General Public License for more details.                                */
1067 /*                                                                          */
1068 /*  You should have received a copy of the GNU General Public License along */
1069 /*  with this program; if not, write to the Free Software Foundation, Inc., */
1070 /*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             */
1071 /* ======================================================================== */
1072 /*                 Copyright (c) 2005-2020, Joseph Zbiciak                  */
1073 /* ======================================================================== */
1074