changeset 1207:9d6f155732ed

Basic support for mapping an analog axis to functionality
author Michael Pavone <pavone@retrodev.com>
date Thu, 26 Jan 2017 23:49:13 -0800
parents 32265f6b79e9
children 95f5253e75c7
files default.cfg io.c io.h render.h render_sdl.c todo.txt
diffstat 6 files changed, 252 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- a/default.cfg	Thu Jan 26 20:30:33 2017 -0800
+++ b/default.cfg	Thu Jan 26 23:49:13 2017 -0800
@@ -54,6 +54,14 @@
 				guide ui.exit
 				leftstick ui.save_state
 			}
+			axes {
+				lefty.positive gamepads.1.down
+				lefty.negative gamepads.1.up
+				leftx.positive gamepads.1.right
+				leftx.negative gamepads.1.left
+				lefttrigger ui.prev_speed
+				righttrigger ui.next_speed
+			}
 		}
 		1 {
 			dpads {
@@ -77,6 +85,14 @@
 				guide ui.exit
 				l3 ui.save_state
 			}
+			axes {
+				lefty.positive gamepads.2.down
+				lefty.negative gamepads.2.up
+				leftx.positive gamepads.2.right
+				leftx.negative gamepads.2.left
+				l2 ui.prev_speed
+				r2 ui.next_speed
+			}
 		}
 	}
 	mice {
--- a/io.c	Thu Jan 26 20:30:33 2017 -0800
+++ b/io.c	Thu Jan 26 23:49:13 2017 -0800
@@ -95,10 +95,18 @@
 } joydpad;
 
 typedef struct {
+	keybinding positive;
+	keybinding negative;
+	int16_t    value;
+} joyaxis;
+
+typedef struct {
 	keybinding *buttons;
 	joydpad    *dpads;
+	joyaxis    *axes;
 	uint32_t   num_buttons; //number of entries in the buttons array, not necessarily the number of buttons on the device
 	uint32_t   num_dpads;   //number of entries in the dpads array, not necessarily the number of dpads on the device
+	uint32_t   num_axes;    //number of entries in the axes array, not necessarily the number of dpads on the device
 } joystick;
 
 typedef struct {
@@ -116,6 +124,14 @@
 static io_port *keyboard_port;
 const uint8_t dpadbits[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT};
 
+static void do_bind(keybinding *binding, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
+{
+	binding->bind_type = bind_type;
+	binding->subtype_a = subtype_a;
+	binding->subtype_b = subtype_b;
+	binding->value = value;
+}
+
 void bind_key(int keycode, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
 {
 	int bucket = keycode >> 15 & 0xFFFF;
@@ -124,10 +140,7 @@
 		memset(bindings[bucket], 0, sizeof(keybinding) * 0x8000);
 	}
 	int idx = keycode & 0x7FFF;
-	bindings[bucket][idx].bind_type = bind_type;
-	bindings[bucket][idx].subtype_a = subtype_a;
-	bindings[bucket][idx].subtype_b = subtype_b;
-	bindings[bucket][idx].value = value;
+	do_bind(bindings[bucket] + idx, bind_type, subtype_a, subtype_b, value);
 }
 
 void bind_button(int joystick, int button, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
@@ -144,10 +157,7 @@
 		joysticks[joystick].buttons = realloc(joysticks[joystick].buttons, sizeof(keybinding) * joysticks[joystick].num_buttons);
 		memset(joysticks[joystick].buttons + old_capacity, 0, joysticks[joystick].num_buttons - old_capacity);
 	}
-	joysticks[joystick].buttons[button].bind_type = bind_type;
-	joysticks[joystick].buttons[button].subtype_a = subtype_a;
-	joysticks[joystick].buttons[button].subtype_b = subtype_b;
-	joysticks[joystick].buttons[button].value = value;
+	do_bind(joysticks[joystick].buttons + button, bind_type, subtype_a, subtype_b, value);
 }
 
 void bind_dpad(int joystick, int dpad, int direction, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
@@ -156,26 +166,45 @@
 		return;
 	}
 	if (!joysticks[joystick].dpads) {
-		//multiple D-pads hats are not common, so don't allocate any extra space
+		//multiple D-pads/hats are not common, so don't allocate any extra space
 		joysticks[joystick].dpads = calloc(dpad+1, sizeof(joydpad));
 		joysticks[joystick].num_dpads = dpad+1;
 	} else if (joysticks[joystick].num_dpads <= dpad) {
 		uint32_t old_capacity = joysticks[joystick].num_dpads;
 		joysticks[joystick].num_dpads *= 2;
 		joysticks[joystick].dpads = realloc(joysticks[joystick].dpads, sizeof(joydpad) * joysticks[joystick].num_dpads);
-		memset(joysticks[joystick].dpads + old_capacity, 0, joysticks[joystick].num_dpads - old_capacity);
+		memset(joysticks[joystick].dpads + old_capacity, 0, (joysticks[joystick].num_dpads - old_capacity) * sizeof(joydpad));
 	}
 	for (int i = 0; i < 4; i ++) {
 		if (dpadbits[i] & direction) {
-			joysticks[joystick].dpads[dpad].bindings[i].bind_type = bind_type;
-			joysticks[joystick].dpads[dpad].bindings[i].subtype_a = subtype_a;
-			joysticks[joystick].dpads[dpad].bindings[i].subtype_b = subtype_b;
-			joysticks[joystick].dpads[dpad].bindings[i].value = value;
+			do_bind(joysticks[joystick].dpads[dpad].bindings + i, bind_type, subtype_a, subtype_b, value);
 			break;
 		}
 	}
 }
 
+void bind_axis(int joystick, int axis, int positive, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b, uint8_t value)
+{
+	if (joystick >= MAX_JOYSTICKS) {
+		return;
+	}
+	if (!joysticks[joystick].axes) {
+		//typical gamepad has 4 axes
+		joysticks[joystick].num_axes = axis+1 > 4 ? axis+1 : 4;
+		joysticks[joystick].axes = calloc(joysticks[joystick].num_axes, sizeof(joyaxis));
+	} else if (joysticks[joystick].num_axes <= axis) {
+		uint32_t old_capacity = joysticks[joystick].num_axes;
+		joysticks[joystick].num_axes *= 2;
+		joysticks[joystick].axes = realloc(joysticks[joystick].axes, sizeof(joyaxis) * joysticks[joystick].num_axes);
+		memset(joysticks[joystick].axes + old_capacity, 0, (joysticks[joystick].num_axes - old_capacity) * sizeof(joyaxis));
+	}
+	if (positive) {
+		do_bind(&joysticks[joystick].axes[axis].positive, bind_type, subtype_a, subtype_b, value);
+	} else {
+		do_bind(&joysticks[joystick].axes[axis].negative, bind_type, subtype_a, subtype_b, value);
+	}
+}
+
 void reset_joystick_bindings(int joystick)
 {
 	if (joystick >= MAX_JOYSTICKS) {
@@ -196,6 +225,13 @@
 			}
 		}
 	}
+	if (joysticks[joystick].axes) {
+		for (int i = 0; i < joysticks[joystick].num_axes; i++)
+		{
+			joysticks[joystick].axes[i].positive.bind_type = BIND_NONE;
+			joysticks[joystick].axes[i].negative.bind_type = BIND_NONE;
+		}
+	}
 }
 
 #define GAMEPAD_BUTTON(PRI_SLOT, SEC_SLOT, VALUE)  (PRI_SLOT << 12 | SEC_SLOT << 8 | VALUE)
@@ -219,8 +255,6 @@
 #define MOUSE_MIDDLE         4
 #define MOUSE_START          8
 
-
-
 void bind_gamepad(int keycode, int gamepadnum, int button)
 {
 
@@ -249,6 +283,15 @@
 	bind_dpad(joystick, dpad, direction, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF);
 }
 
+void bind_axis_gamepad(int joystick, int axis, uint8_t positive, int gamepadnum, int button)
+{
+	if (gamepadnum < 1 || gamepadnum > 8) {
+		return;
+	}
+	uint8_t bind_type = gamepadnum - 1 + BIND_GAMEPAD1;
+	bind_axis(joystick, axis, positive, bind_type, button >> 12, button >> 8 & 0xF, button & 0xFF);
+}
+
 void bind_ui(int keycode, ui_action action, uint8_t param)
 {
 	bind_key(keycode, BIND_UI, action, 0, param);
@@ -264,6 +307,11 @@
 	bind_dpad(joystick, dpad, direction, BIND_UI, action, 0, param);
 }
 
+void bind_axis_ui(int joystick, int axis, uint8_t positive, ui_action action, uint8_t param)
+{
+	bind_axis(joystick, axis, positive, BIND_UI, action, 0, param);
+}
+
 void handle_binding_down(keybinding * binding)
 {
 	if (binding->bind_type >= BIND_GAMEPAD1 && binding->bind_type <= BIND_GAMEPAD8)
@@ -466,6 +514,29 @@
 	}
 }
 
+#define JOY_AXIS_THRESHOLD 2000
+
+void handle_joy_axis(int joystick, int axis, int16_t value)
+{
+	if (joystick >= MAX_JOYSTICKS  || axis >= joysticks[joystick].num_axes) {
+		return;
+	}
+	joyaxis *jaxis = joysticks[joystick].axes + axis;
+	int old_active = abs(jaxis->value) > JOY_AXIS_THRESHOLD;
+	int new_active = abs(value) > JOY_AXIS_THRESHOLD;
+	int old_pos = jaxis->value > 0;
+	int new_pos = value > 0;
+	jaxis->value = value;
+	if (old_active && (!new_active || old_pos != new_pos)) {
+		//previously activated direction is no longer active
+		handle_binding_up(old_pos ? &jaxis->positive : &jaxis->negative);
+	}
+	if (new_active && (!old_active || old_pos != new_pos)) {
+		//previously unactivated direction is now active
+		handle_binding_down(new_pos ? &jaxis->positive : &jaxis->negative);
+	}
+}
+
 void handle_mouseup(int mouse, int button)
 {
 	if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) {
@@ -967,7 +1038,7 @@
 	long hostbutton = strtol(key, &end, 10);
 	if (*end) {
 		//key is not a valid base 10 integer
-		hostbutton = render_translate_input_name(hostpadnum, key);
+		hostbutton = render_translate_input_name(hostpadnum, key, 0);
 		if (hostbutton < 0) {
 			if (hostbutton == RENDER_INVALID_NAME) {
 				warning("%s is not a valid gamepad input name\n", key);
@@ -982,8 +1053,13 @@
 			} else {
 				bind_dpad_ui(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), ui_func, button);
 			}
+			return;
 		} else if (hostbutton & RENDER_AXIS_BIT) {
-			//TODO: support analog axes
+			if (bindtype == BIND_GAMEPAD1) {
+				bind_axis_gamepad(hostpadnum, render_axis_part(hostbutton), 1, padnum, button);
+			} else {
+				bind_axis_ui(hostpadnum, render_axis_part(hostbutton), 1, padnum, button);
+			}
 			return;
 		}
 	}
@@ -994,6 +1070,65 @@
 	}
 }
 
+void process_pad_axis(char *key, tern_val val, void *data)
+{
+	key = strdup(key);
+	pad_button_state *state = data;
+	int hostpadnum = state->padnum;
+	int ui_func, padnum, button;
+	int bindtype = parse_binding_target(val.ptrval, state->padbuttons, state->mousebuttons, &ui_func, &padnum, &button);
+	char *modifier = strchr(key, '.');
+	int positive = 1;
+	if (modifier) {
+		*modifier = 0;
+		modifier++;
+		if (!strcmp("negative", modifier)) {
+			positive = 0;
+		} else if(strcmp("positive", modifier)) {
+			warning("Invalid axis modifier %s for axis %s on pad %d\n", modifier, key, hostpadnum);
+		}
+	}
+	char *end;
+	long axis = strtol(key, &end, 10);
+	if (*end) {
+		//key is not a valid base 10 integer
+		axis = render_translate_input_name(hostpadnum, key, 1);
+		if (axis < 0) {
+			if (axis == RENDER_INVALID_NAME) {
+				warning("%s is not a valid gamepad input name\n", key);
+			} else if (axis == RENDER_NOT_MAPPED) {
+				warning("No mapping exists for input %s on gamepad %d\n", key, hostpadnum);
+			}
+			goto done;
+		}
+		if (axis & RENDER_DPAD_BIT) {
+			if (bindtype == BIND_GAMEPAD1) {
+				bind_dpad_gamepad(hostpadnum, render_dpad_part(axis), render_direction_part(axis), padnum, button);
+			} else {
+				bind_dpad_ui(hostpadnum, render_dpad_part(axis), render_direction_part(axis), ui_func, button);
+			}
+			goto done;
+		} else if (axis & RENDER_AXIS_BIT) {
+			axis = render_axis_part(axis);
+		} else {
+			if (bindtype == BIND_GAMEPAD1) {
+				bind_button_gamepad(hostpadnum, axis, padnum, button);
+			} else if (bindtype == BIND_UI) {
+				bind_button_ui(hostpadnum, axis, ui_func, button);
+			}
+			goto done;
+		}
+	}
+	if (bindtype == BIND_GAMEPAD1) {
+		bind_axis_gamepad(hostpadnum, axis, positive, padnum, button);
+	} else {
+		bind_axis_ui(hostpadnum, axis, positive, ui_func, button);
+	}
+done:
+	free(key);
+	return;
+}
+
 static tern_node *get_pad_buttons()
 {
 	static tern_node *padbuttons;
@@ -1070,16 +1205,33 @@
 				};
 				tern_foreach(button_node, process_pad_button, &state);
 			}
+			tern_node *axes_node = tern_find_ptr(pad, "axes");
+			if (axes_node) {
+				pad_button_state state = {
+					.padnum = joystick,
+					.padbuttons = get_pad_buttons(),
+					.mousebuttons = get_mouse_buttons()
+				};
+				tern_foreach(axes_node, process_pad_axis, &state);
+			}
 			if (current_io) {
 				if (joysticks[joystick].buttons) {
 					map_bindings(current_io->ports, joysticks[joystick].buttons, joysticks[joystick].num_buttons);
 				}
 				if (joysticks[joystick].dpads)
 				{
-					for (uint32_t i = 0; i < joysticks[joystick].num_dpads; i++) {
+					for (uint32_t i = 0; i < joysticks[joystick].num_dpads; i++)
+					{
 						map_bindings(current_io->ports, joysticks[joystick].dpads[i].bindings, 4);
 					}
 				}
+				if (joysticks[joystick].axes) {
+					for (uint32_t i = 0; i < joysticks[joystick].num_axes; i++)
+					{
+						map_bindings(current_io->ports, &joysticks[joystick].axes[i].positive, 1);
+						map_bindings(current_io->ports, &joysticks[joystick].axes[i].negative, 1);
+					}
+				}
 			}
 		}
 	}
@@ -1193,10 +1345,16 @@
 		}
 		if (joysticks[stick].dpads)
 		{
-			for (uint32_t i = 0; i < joysticks[stick].num_dpads; i++) {
+			for (uint32_t i = 0; i < joysticks[stick].num_dpads; i++)
+			{
 				map_bindings(ports, joysticks[stick].dpads[i].bindings, 4);
 			}
 		}
+		for (uint32_t i = 0; i < joysticks[stick].num_axes; i++)
+		{
+			map_bindings(current_io->ports, &joysticks[stick].axes[i].positive, 1);
+			map_bindings(current_io->ports, &joysticks[stick].axes[i].negative, 1);
+		}
 	}
 	for (int mouse = 0; mouse < MAX_MICE; mouse++)
 	{
--- a/io.h	Thu Jan 26 20:30:33 2017 -0800
+++ b/io.h	Thu Jan 26 23:49:13 2017 -0800
@@ -95,6 +95,7 @@
 void handle_joydown(int joystick, int button);
 void handle_joyup(int joystick, int button);
 void handle_joy_dpad(int joystick, int dpad, uint8_t state);
+void handle_joy_axis(int joystick, int axis, int16_t value);
 void handle_joy_added(int joystick);
 void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay);
 void handle_mousedown(int mouse, int button);
--- a/render.h	Thu Jan 26 20:30:33 2017 -0800
+++ b/render.h	Thu Jan 26 23:49:13 2017 -0800
@@ -88,8 +88,9 @@
 int render_height();
 int render_fullscreen();
 void process_events();
-int32_t render_translate_input_name(int32_t controller, char *name);
+int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis);
 int32_t render_dpad_part(int32_t input);
+int32_t render_axis_part(int32_t input);
 uint8_t render_direction_part(int32_t input);
 void render_errorbox(char *title, char *message);
 void render_warnbox(char *title, char *message);
--- a/render_sdl.c	Thu Jan 26 20:30:33 2017 -0800
+++ b/render_sdl.c	Thu Jan 26 23:49:13 2017 -0800
@@ -656,9 +656,9 @@
 	return -1;
 }
 
-int32_t render_translate_input_name(int32_t controller, char *name)
+int32_t render_translate_input_name(int32_t controller, char *name, uint8_t is_axis)
 {
-	static tern_node *button_lookup;
+	static tern_node *button_lookup, *axis_lookup;
 	if (controller > MAX_JOYSTICKS || !joysticks[controller]) {
 		return RENDER_NOT_PLUGGED_IN;
 	}
@@ -666,35 +666,55 @@
 	if (!SDL_IsGameController(joystick_sdl_index[controller])) {
 		return RENDER_NOT_MAPPED;
 	}
-	
-	if (!button_lookup) {
-		for (int i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++)
-		{
-			button_lookup = tern_insert_int(button_lookup, SDL_GameControllerGetStringForButton(i), i);
-		}
-		//alternative Playstation-style names
-		button_lookup = tern_insert_int(button_lookup, "cross", SDL_CONTROLLER_BUTTON_A);
-		button_lookup = tern_insert_int(button_lookup, "circle", SDL_CONTROLLER_BUTTON_B);
-		button_lookup = tern_insert_int(button_lookup, "square", SDL_CONTROLLER_BUTTON_X);
-		button_lookup = tern_insert_int(button_lookup, "triangle", SDL_CONTROLLER_BUTTON_Y);
-		button_lookup = tern_insert_int(button_lookup, "share", SDL_CONTROLLER_BUTTON_BACK);
-		button_lookup = tern_insert_int(button_lookup, "select", SDL_CONTROLLER_BUTTON_BACK);
-		button_lookup = tern_insert_int(button_lookup, "options", SDL_CONTROLLER_BUTTON_START);
-		button_lookup = tern_insert_int(button_lookup, "l1", SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
-		button_lookup = tern_insert_int(button_lookup, "r1", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
-		button_lookup = tern_insert_int(button_lookup, "l3", SDL_CONTROLLER_BUTTON_LEFTSTICK);
-		button_lookup = tern_insert_int(button_lookup, "r3", SDL_CONTROLLER_BUTTON_RIGHTSTICK);
-	}
-	intptr_t sdl_button = tern_find_int(button_lookup, name, SDL_CONTROLLER_BUTTON_INVALID);
-	if (sdl_button == SDL_CONTROLLER_BUTTON_INVALID) {
-		return RENDER_INVALID_NAME;
-	}
 	SDL_GameController *control = SDL_GameControllerOpen(joystick_sdl_index[controller]);
 	if (!control) {
 		warning("Failed to open game controller %d: %s\n", controller, SDL_GetError());
 		return RENDER_NOT_PLUGGED_IN;
 	}
-	SDL_GameControllerButtonBind cbind = SDL_GameControllerGetBindForButton(control, sdl_button);
+	
+	SDL_GameControllerButtonBind cbind;
+	if (is_axis) {
+		if (!axis_lookup) {
+			for (int i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++)
+			{
+				axis_lookup = tern_insert_int(axis_lookup, SDL_GameControllerGetStringForAxis(i), i);
+			}
+			//alternative Playstation-style names
+			axis_lookup = tern_insert_int(axis_lookup, "l2", SDL_CONTROLLER_AXIS_TRIGGERLEFT);
+			axis_lookup = tern_insert_int(axis_lookup, "r2", SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
+		}
+		intptr_t sdl_axis = tern_find_int(axis_lookup, name, SDL_CONTROLLER_AXIS_INVALID);
+		if (sdl_axis == SDL_CONTROLLER_AXIS_INVALID) {
+			SDL_GameControllerClose(control);
+			return RENDER_INVALID_NAME;
+		}
+		cbind = SDL_GameControllerGetBindForAxis(control, sdl_axis);
+	} else {
+		if (!button_lookup) {
+			for (int i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++)
+			{
+				button_lookup = tern_insert_int(button_lookup, SDL_GameControllerGetStringForButton(i), i);
+			}
+			//alternative Playstation-style names
+			button_lookup = tern_insert_int(button_lookup, "cross", SDL_CONTROLLER_BUTTON_A);
+			button_lookup = tern_insert_int(button_lookup, "circle", SDL_CONTROLLER_BUTTON_B);
+			button_lookup = tern_insert_int(button_lookup, "square", SDL_CONTROLLER_BUTTON_X);
+			button_lookup = tern_insert_int(button_lookup, "triangle", SDL_CONTROLLER_BUTTON_Y);
+			button_lookup = tern_insert_int(button_lookup, "share", SDL_CONTROLLER_BUTTON_BACK);
+			button_lookup = tern_insert_int(button_lookup, "select", SDL_CONTROLLER_BUTTON_BACK);
+			button_lookup = tern_insert_int(button_lookup, "options", SDL_CONTROLLER_BUTTON_START);
+			button_lookup = tern_insert_int(button_lookup, "l1", SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
+			button_lookup = tern_insert_int(button_lookup, "r1", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
+			button_lookup = tern_insert_int(button_lookup, "l3", SDL_CONTROLLER_BUTTON_LEFTSTICK);
+			button_lookup = tern_insert_int(button_lookup, "r3", SDL_CONTROLLER_BUTTON_RIGHTSTICK);
+		}
+		intptr_t sdl_button = tern_find_int(button_lookup, name, SDL_CONTROLLER_BUTTON_INVALID);
+		if (sdl_button == SDL_CONTROLLER_BUTTON_INVALID) {
+			SDL_GameControllerClose(control);
+			return RENDER_INVALID_NAME;
+		}
+		cbind = SDL_GameControllerGetBindForButton(control, sdl_button);
+	}
 	SDL_GameControllerClose(control);
 	switch (cbind.bindType)
 	{
@@ -710,7 +730,7 @@
 
 int32_t render_dpad_part(int32_t input)
 {
-	return input >> 4 & 0x7FFFFF;
+	return input >> 4 & 0xFFFFFF;
 }
 
 uint8_t render_direction_part(int32_t input)
@@ -718,6 +738,11 @@
 	return input & 0xF;
 }
 
+int32_t render_axis_part(int32_t input)
+{
+	return input & 0xFFFFFFF;
+}
+
 static uint8_t scancode_map[SDL_NUM_SCANCODES] = {
 	[SDL_SCANCODE_A] = 0x1C,
 	[SDL_SCANCODE_B] = 0x32,
@@ -840,6 +865,9 @@
 	case SDL_JOYHATMOTION:
 		handle_joy_dpad(find_joystick_index(event->jbutton.which), event->jhat.hat, event->jhat.value);
 		break;
+	case SDL_JOYAXISMOTION:
+		handle_joy_axis(find_joystick_index(event->jaxis.which), event->jaxis.axis, event->jaxis.value);
+		break;
 	case SDL_JOYDEVICEADDED:
 		if (event->jdevice.which < MAX_JOYSTICKS) {
 			int index = lowest_unused_joystick_index();
--- a/todo.txt	Thu Jan 26 20:30:33 2017 -0800
+++ b/todo.txt	Thu Jan 26 23:49:13 2017 -0800
@@ -20,7 +20,6 @@
 Horizontal border emulation
 Integrate Jaguar emulation into main executable
 Realtec mapper support
-Gamepad analog axis mapping
 64-bit Windows Build
 
 Future Releases (no particular order)