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