shithub: psxe

Download patch

ref: a7f16c04ed295239ee3e8fd9a203b774388e98fc
parent: df475e3016890ab1600b51bb0eeed6ab7ce27e01
author: allkern <lisandroaalarcon@gmail.com>
date: Mon May 6 20:51:02 EDT 2024

Assorted fixes and improvements

Implemented controller support
Implemented SDA analog mode (WIP)
Fixed polygon edge seam
Fixed SPU note length issue
Fixed small XA-ADPCM stutter right when starting playback

--- a/Makefile
+++ b/Makefile
@@ -24,6 +24,7 @@
 
 	gcc $(SOURCES) -o bin/psxe \
 		-I"." \
+		-I"psx" \
 		-DOS_INFO="$(OS_INFO)" \
 		-DREP_VERSION="$(VERSION_TAG)" \
 		-DREP_COMMIT_HASH="$(COMMIT_HASH)" \
--- a/build-win32.ps1
+++ b/build-win32.ps1
@@ -10,6 +10,7 @@
 mkdir -Force -Path bin > $null
 
 gcc -I"`"$($PSX_DIR)`"" `
+    -I"`"$($PSX_DIR)\psx`"" `
     -I"`"$($SDL2_DIR)\include`"" `
     -I"`"$($SDL2_DIR)\include\SDL2`"" `
     "psx\*.c" `
--- a/build-win64.ps1
+++ b/build-win64.ps1
@@ -10,6 +10,7 @@
 mkdir -Force -Path bin > $null
 
 gcc -I"`"$($PSX_DIR)`"" `
+    -I"`"$($PSX_DIR)\psx`"" `
     -I"`"$($SDL2_DIR)\include`"" `
     -I"`"$($SDL2_DIR)\include\SDL2`"" `
     "psx\*.c" `
@@ -25,6 +26,6 @@
     -m64 -lSDL2main -lSDL2 -Wno-overflow `
     -Wall -pedantic -DLOG_USE_COLOR `
     -Wno-address-of-packed-member `
-    -ffast-math -Ofast -g
+    -ffast-math -Ofast -g -flto
 
 Copy-Item -Path "sdl2-win64/SDL2.dll" -Destination "bin"
\ No newline at end of file
--- a/frontend/config.c
+++ b/frontend/config.c
@@ -4,6 +4,8 @@
 #include "config.h"
 #include "common.h"
 
+#include "log.h"
+
 static const char* g_version_text =
 #ifdef _WIN32
     "psxe.exe (" STR(OS_INFO) ") " STR(REP_VERSION) "-" STR(REP_COMMIT_HASH) "\n"
--- a/frontend/config.h
+++ b/frontend/config.h
@@ -5,7 +5,6 @@
 
 #include "argparse.h"
 #include "toml.h"
-#include "psx/log.h"
 
 typedef struct {
     int use_args;
--- a/frontend/main.c
+++ b/frontend/main.c
@@ -1,6 +1,6 @@
-#include "psx/psx.h"
-#include "psx/input/sda.h"
-#include "psx/disc/cue.h"
+#include "psx.h"
+#include "input/sda.h"
+#include "disc/cue.h"
 
 #include "screen.h"
 #include "config.h"
--- a/frontend/screen.c
+++ b/frontend/screen.c
@@ -1,6 +1,8 @@
 #include "screen.h"
 
-uint16_t screen_get_button(SDL_Keycode k) {
+#include "input/sda.h"
+
+uint32_t screen_get_button(SDL_Keycode k) {
     if (k == SDLK_x     ) return PSXI_SW_SDA_CROSS;
     if (k == SDLK_a     ) return PSXI_SW_SDA_SQUARE;
     if (k == SDLK_w     ) return PSXI_SW_SDA_TRIANGLE;
@@ -17,10 +19,40 @@
     if (k == SDLK_3     ) return PSXI_SW_SDA_R2;
     if (k == SDLK_z     ) return PSXI_SW_SDA_L3;
     if (k == SDLK_c     ) return PSXI_SW_SDA_R3;
+    if (k == SDLK_2     ) return PSXI_SW_SDA_ANALOG;
 
     return 0;
 }
 
+uint32_t screen_get_button_joystick(uint8_t b) {
+    if (b == SDL_CONTROLLER_BUTTON_A            ) return PSXI_SW_SDA_CROSS;
+    if (b == SDL_CONTROLLER_BUTTON_X            ) return PSXI_SW_SDA_SQUARE;
+    if (b == SDL_CONTROLLER_BUTTON_Y            ) return PSXI_SW_SDA_TRIANGLE;
+    if (b == SDL_CONTROLLER_BUTTON_B            ) return PSXI_SW_SDA_CIRCLE;
+    if (b == SDL_CONTROLLER_BUTTON_START        ) return PSXI_SW_SDA_START;
+    if (b == SDL_CONTROLLER_BUTTON_GUIDE        ) return PSXI_SW_SDA_SELECT;
+    if (b == SDL_CONTROLLER_BUTTON_DPAD_UP      ) return PSXI_SW_SDA_PAD_UP;
+    if (b == SDL_CONTROLLER_BUTTON_DPAD_DOWN    ) return PSXI_SW_SDA_PAD_DOWN;
+    if (b == SDL_CONTROLLER_BUTTON_DPAD_LEFT    ) return PSXI_SW_SDA_PAD_LEFT;
+    if (b == SDL_CONTROLLER_BUTTON_DPAD_RIGHT   ) return PSXI_SW_SDA_PAD_RIGHT;
+    if (b == SDL_CONTROLLER_BUTTON_LEFTSHOULDER ) return PSXI_SW_SDA_L1;
+    if (b == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) return PSXI_SW_SDA_R1;
+    if (b == SDL_CONTROLLER_AXIS_TRIGGERLEFT    ) return PSXI_SW_SDA_L2; // Can't map these yet
+    if (b == SDL_CONTROLLER_AXIS_TRIGGERRIGHT   ) return PSXI_SW_SDA_R2; // Can't map these yet
+    if (b == SDL_CONTROLLER_BUTTON_LEFTSTICK    ) return PSXI_SW_SDA_L3;
+    if (b == SDL_CONTROLLER_BUTTON_RIGHTSTICK   ) return PSXI_SW_SDA_R3;
+
+    return 0;
+} 
+
+SDL_GameController* screen_find_controller() {
+    for (int i = 0; i < SDL_NumJoysticks(); i++)
+        if (SDL_IsGameController(i))
+            return SDL_GameControllerOpen(i);
+
+    return NULL;
+}
+
 int screen_get_base_width(psxe_screen_t* screen) {
     int width = psx_get_dmode_width(screen->psx);
 
@@ -51,8 +83,10 @@
     screen->texture_width = PSX_GPU_FB_WIDTH;
     screen->texture_height = PSX_GPU_FB_HEIGHT;
 
-    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
+    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER);
     SDL_SetRenderDrawColor(screen->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
+
+    screen->controller = screen_find_controller();
 }
 
 void psxe_screen_reload(psxe_screen_t* screen) {
@@ -152,6 +186,9 @@
     //     screen->psx->gpu->disp_y2 - screen->psx->gpu->disp_y1
     // );
 
+    if ((screen->psx->gpu->disp_y + screen->texture_height) > 512)
+        display_buf = psx_get_vram(screen->psx);
+
     SDL_UpdateTexture(screen->texture, NULL, display_buf, PSX_GPU_FB_STRIDE);
     SDL_RenderClear(screen->renderer);
 
@@ -248,21 +285,85 @@
                     } break;
                 }
 
-                uint16_t mask = screen_get_button(event.key.keysym.sym);
+                uint32_t mask = screen_get_button(event.key.keysym.sym);
 
                 psx_pad_button_press(screen->pad, 0, mask);
 
-                if (event.key.keysym.sym == SDLK_RETURN) {
+                if (event.key.keysym.sym == SDLK_RETURN)
                     psx_exp2_atcons_put(screen->psx->exp2, 13);
+            } break;
+
+            case SDL_CONTROLLERDEVICEADDED: {
+                if (!screen->controller)
+                    screen->controller = SDL_GameControllerOpen(event.cdevice.which);
+            } break;
+
+            case SDL_CONTROLLERDEVICEREMOVED: {
+                SDL_Joystick* joy = SDL_GameControllerGetJoystick(screen->controller);
+                SDL_JoystickID id = SDL_JoystickInstanceID(joy);
+
+                if (screen->controller && (event.cdevice.which == id)) {
+                    SDL_GameControllerClose(screen->controller);
+
+                    screen->controller = screen_find_controller();
                 }
             } break;
 
+            case SDL_CONTROLLERBUTTONDOWN: {
+                SDL_Joystick* joy = SDL_GameControllerGetJoystick(screen->controller);
+                SDL_JoystickID id = SDL_JoystickInstanceID(joy);
+
+                if (screen->controller && (event.cdevice.which == id)) {
+                    uint32_t mask = screen_get_button_joystick(event.cbutton.button);
+
+                    psx_pad_button_press(screen->pad, 0, mask);
+                }
+            } break;
+
+            case SDL_CONTROLLERBUTTONUP: {
+                SDL_Joystick* joy = SDL_GameControllerGetJoystick(screen->controller);
+                SDL_JoystickID id = SDL_JoystickInstanceID(joy);
+
+                if (screen->controller && (event.cdevice.which == id)) {
+                    uint32_t mask = screen_get_button_joystick(event.cbutton.button);
+
+                    psx_pad_button_release(screen->pad, 0, mask);
+                }
+            } break;
+
+            case SDL_CONTROLLERAXISMOTION: {
+                SDL_Joystick* joy = SDL_GameControllerGetJoystick(screen->controller);
+                SDL_JoystickID id = SDL_JoystickInstanceID(joy);
+
+                if (screen->controller && (event.cdevice.which == id)) {
+                    int mapped_axis = ((int)event.caxis.value + INT16_MAX + 1) / 0x100;
+
+                    switch (event.caxis.axis) {
+                        case SDL_CONTROLLER_AXIS_RIGHTX:
+                            psx_pad_analog_change(screen->pad, 0, PSXI_AX_SDA_RIGHT_HORZ, mapped_axis);
+                        break;
+
+                        case SDL_CONTROLLER_AXIS_RIGHTY:
+                            psx_pad_analog_change(screen->pad, 0, PSXI_AX_SDA_RIGHT_VERT, mapped_axis);
+                        break;
+
+                        case SDL_CONTROLLER_AXIS_LEFTX:
+                            psx_pad_analog_change(screen->pad, 0, PSXI_AX_SDA_LEFT_HORZ, mapped_axis);
+                        break;
+
+                        case SDL_CONTROLLER_AXIS_LEFTY:
+                            psx_pad_analog_change(screen->pad, 0, PSXI_AX_SDA_LEFT_VERT, mapped_axis);
+                        break;
+                    }
+                }
+            } break;
+
             case SDL_TEXTINPUT: {
                 psx_exp2_atcons_put(screen->psx->exp2, event.text.text[0]);
             } break;
 
             case SDL_KEYUP: {
-                uint16_t mask = screen_get_button(event.key.keysym.sym);
+                uint32_t mask = screen_get_button(event.key.keysym.sym);
 
                 psx_pad_button_release(screen->pad, 0, mask);
             } break;
@@ -291,6 +392,22 @@
 
 void psxe_gpu_dmode_event_cb(psx_gpu_t* gpu) {
     psxe_screen_t* screen = gpu->udata[0];
+
+    // printf("res=(%u,%u) off=(%u,%u) disp=(%u,%u-%u,%u) draw=(%u,%u-%u,%u) vres=%u\n",
+    //     screen->texture_width,
+    //     screen->texture_height,
+    //     screen->psx->gpu->disp_x,
+    //     screen->psx->gpu->disp_y,
+    //     screen->psx->gpu->disp_x1,
+    //     screen->psx->gpu->disp_y1,
+    //     screen->psx->gpu->disp_x2,
+    //     screen->psx->gpu->disp_y2,
+    //     screen->psx->gpu->draw_x1,
+    //     screen->psx->gpu->draw_y1,
+    //     screen->psx->gpu->draw_x2,
+    //     screen->psx->gpu->draw_y2,
+    //     screen->psx->gpu->disp_y2 - screen->psx->gpu->disp_y1
+    // );
 
     screen->format = psx_get_display_format(screen->psx) ?
         SDL_PIXELFORMAT_RGB24 : SDL_PIXELFORMAT_BGR555;
--- a/frontend/screen.h
+++ b/frontend/screen.h
@@ -1,12 +1,13 @@
 #ifndef SCREEN_H
 #define SCREEN_H
 
-#include "psx/psx.h"
+#include "psx.h"
 #include "common.h"
 
 #include <string.h>
 
 #include "SDL2/SDL.h"
+#include "SDL2/SDL_gamecontroller.h"
 
 typedef struct {
     SDL_Window* window;
@@ -28,6 +29,8 @@
     int vertical_mode;
     int debug_mode;
     int open;
+
+    SDL_GameController* controller;
 } psxe_screen_t;
 
 psxe_screen_t* psxe_screen_create();
--- a/psx/dev/cdrom.c
+++ b/psx/dev/cdrom.c
@@ -460,6 +460,7 @@
             if (cdrom->mode & MODE_XA_ADPCM) {
                 cdrom->xa_msf = cdrom->seek_msf;
                 cdrom->xa_playing = 1;
+                cdrom->xa_remaining_samples = 0;
 
                 SET_BITS(status, STAT_ADPBUSY_MASK, STAT_ADPBUSY_MASK);
 
@@ -1311,6 +1312,7 @@
             if (cdrom->mode & MODE_XA_ADPCM) {
                 cdrom->xa_msf = cdrom->seek_msf;
                 cdrom->xa_playing = 1;
+                cdrom->xa_remaining_samples = 0;
 
                 SET_BITS(status, STAT_ADPBUSY_MASK, STAT_ADPBUSY_MASK);
 
@@ -1606,17 +1608,17 @@
 }
 
 void cdrom_write_cmd(psx_cdrom_t* cdrom, uint8_t value) {
-    printf("%s(%02x) %u params=[%02x, %02x, %02x, %02x, %02x, %02x]\n",
-        g_psx_cdrom_command_names[value],
-        value,
-        cdrom->pfifo_index,
-        cdrom->pfifo[0],
-        cdrom->pfifo[1],
-        cdrom->pfifo[2],
-        cdrom->pfifo[3],
-        cdrom->pfifo[4],
-        cdrom->pfifo[5]
-    );
+    // printf("%s(%02x) %u params=[%02x, %02x, %02x, %02x, %02x, %02x]\n",
+    //     g_psx_cdrom_command_names[value],
+    //     value,
+    //     cdrom->pfifo_index,
+    //     cdrom->pfifo[0],
+    //     cdrom->pfifo[1],
+    //     cdrom->pfifo[2],
+    //     cdrom->pfifo[3],
+    //     cdrom->pfifo[4],
+    //     cdrom->pfifo[5]
+    // );
 
     cdrom->command = value;
     cdrom->state = CD_STATE_RECV_CMD;
--- a/psx/dev/dma.c
+++ b/psx/dev/dma.c
@@ -346,6 +346,7 @@
     if (!size) {
         printf("0 sized CDROM DMA\n");
 
+        return;
         exit(1);
     }
 
--- a/psx/dev/gpu.c
+++ b/psx/dev/gpu.c
@@ -27,15 +27,15 @@
 // #define BGR555(c) gpu_to_bgr555(c)
 
 int min3(int a, int b, int c) {
-    int m = a <= b ? a : b;
+    int m = (a <= b) ? a : b;
 
-    return m <= c ? m : c;
+    return (m <= c) ? m : c;
 }
 
 int max3(int a, int b, int c) {
-    int m = a >= b ? a : b;
+    int m = (a > b) ? a : b;
 
-    return m >= c ? m : c;
+    return (m > c) ? m : c;
 }
 
 psx_gpu_t* psx_gpu_create() {
@@ -171,7 +171,7 @@
     }
 }
 
-void gpu_render_triangle(psx_gpu_t* gpu, vertex_t v0, vertex_t v1, vertex_t v2, poly_data_t data) {
+void gpu_render_triangle(psx_gpu_t* gpu, vertex_t v0, vertex_t v1, vertex_t v2, poly_data_t data, int edge) {
     vertex_t a, b, c, p;
 
     int tpx = (data.texp & 0xf) << 6;
@@ -200,23 +200,17 @@
     }
 
     a.x += gpu->off_x;
-    a.y += gpu->off_y;
     b.x += gpu->off_x;
-    b.y += gpu->off_y;
     c.x += gpu->off_x;
+    a.y += gpu->off_y;
+    b.y += gpu->off_y;
     c.y += gpu->off_y;
 
-    int xmin = max(min3(a.x, b.x, c.x), gpu->draw_x1);
-    int ymin = max(min3(a.y, b.y, c.y), gpu->draw_y1);
-    int xmax = min(max3(a.x, b.x, c.x), gpu->draw_x2);
-    int ymax = min(max3(a.y, b.y, c.y), gpu->draw_y2);
+    int xmin = min3(a.x, b.x, c.x);
+    int ymin = min3(a.y, b.y, c.y);
+    int xmax = max3(a.x, b.x, c.x);
+    int ymax = max3(a.y, b.y, c.y);
 
-    // Hack
-    if (!(data.attrib & PA_TEXTURED)) {
-        ++xmax;
-        ++ymax;
-    }
-
     float area = EDGE(a, b, c);
 
     for (int y = ymin; y < ymax; y++) {
@@ -234,7 +228,12 @@
             float z1 = EDGE(c, a, p);
             float z2 = EDGE(a, b, p);
 
-            if ((z0 < 0) || (z1 < 0) || (z2 < 0))
+            int e = ((z0 < 0) || (z1 < 0) || (z2 < 0));
+
+            if (transp && edge)
+                e = ((z0 < 0) || (z1 <= 0) || (z2 < 0));
+
+            if (e)
                 continue;
 
             uint16_t color = 0;
@@ -275,31 +274,34 @@
                 if (!texel)
                     continue;
 
-                if (transp) {
+                if (transp)
                     transp = (texel & 0x8000) != 0;
-                }
 
                 if (data.attrib & PA_RAW) {
                     color = texel;
                 } else {
-                    int tr = ((texel >> 0 ) & 0x1f) << 3;
-                    int tg = ((texel >> 5 ) & 0x1f) << 3;
-                    int tb = ((texel >> 10) & 0x1f) << 3;
+                    float tr = ((texel >> 0 ) & 0x1f) << 3;
+                    float tg = ((texel >> 5 ) & 0x1f) << 3;
+                    float tb = ((texel >> 10) & 0x1f) << 3;
 
-                    int mr = (mod >> 0 ) & 0xff;
-                    int mg = (mod >> 8 ) & 0xff;
-                    int mb = (mod >> 16) & 0xff;
+                    float mr = (mod >> 0 ) & 0xff;
+                    float mg = (mod >> 8 ) & 0xff;
+                    float mb = (mod >> 16) & 0xff;
 
-                    int cr = (tr * mr) / 0x80;
-                    int cg = (tg * mg) / 0x80;
-                    int cb = (tb * mb) / 0x80;
+                    float cr = (tr * mr) / 128.0f;
+                    float cg = (tg * mg) / 128.0f;
+                    float cb = (tb * mb) / 128.0f;
 
-                    cr = (cr >= 0xff) ? 0xff : ((cr <= 0) ? 0 : cr);
-                    cg = (cg >= 0xff) ? 0xff : ((cg <= 0) ? 0 : cg);
-                    cb = (cb >= 0xff) ? 0xff : ((cb <= 0) ? 0 : cb);
+                    cr = (cr >= 255.0f) ? 255.0f : ((cr <= 0.0f) ? 0.0f : cr);
+                    cg = (cg >= 255.0f) ? 255.0f : ((cg <= 0.0f) ? 0.0f : cg);
+                    cb = (cb >= 255.0f) ? 255.0f : ((cb <= 0.0f) ? 0.0f : cb);
 
-                    uint32_t rgb = cr | (cg << 8) | (cb << 16);
+                    unsigned int ucr = cr;
+                    unsigned int ucg = cg;
+                    unsigned int ucb = cb;
 
+                    uint32_t rgb = ucr | (ucg << 8) | (ucb << 16);
+
                     color = BGR555(rgb);
                 }
             } else {
@@ -306,16 +308,16 @@
                 color = BGR555(mod);
             }
 
-            int cr = ((color >> 0 ) & 0x1f) << 3;
-            int cg = ((color >> 5 ) & 0x1f) << 3;
-            int cb = ((color >> 10) & 0x1f) << 3;
+            float cr = ((color >> 0 ) & 0x1f) << 3;
+            float cg = ((color >> 5 ) & 0x1f) << 3;
+            float cb = ((color >> 10) & 0x1f) << 3;
 
             if (transp) {
                 uint16_t back = gpu->vram[x + (y * 1024)];
 
-                int br = ((back >> 0 ) & 0x1f) << 3;
-                int bg = ((back >> 5 ) & 0x1f) << 3;
-                int bb = ((back >> 10) & 0x1f) << 3;
+                float br = ((back >> 0 ) & 0x1f) << 3;
+                float bg = ((back >> 5 ) & 0x1f) << 3;
+                float bb = ((back >> 10) & 0x1f) << 3;
 
                 // Do we use transp or gpustat here?
                 switch (transp_mode) {
@@ -341,12 +343,16 @@
                     } break;
                 }
 
-                cr = (cr >= 0xff) ? 0xff : ((cr <= 0) ? 0 : cr);
-                cg = (cg >= 0xff) ? 0xff : ((cg <= 0) ? 0 : cg);
-                cb = (cb >= 0xff) ? 0xff : ((cb <= 0) ? 0 : cb);
+                cr = (cr >= 255.0f) ? 255.0f : ((cr <= 0.0f) ? 0.0f : cr);
+                cg = (cg >= 255.0f) ? 255.0f : ((cg <= 0.0f) ? 0.0f : cg);
+                cb = (cb >= 255.0f) ? 255.0f : ((cb <= 0.0f) ? 0.0f : cb);
 
-                uint32_t rgb = cr | (cg << 8) | (cb << 16);
+                unsigned int ucr = cr;
+                unsigned int ucg = cg;
+                unsigned int ucb = cb;
 
+                uint32_t rgb = ucr | (ucg << 8) | (ucb << 16);
+
                 color = BGR555(rgb);
             }
 
@@ -355,7 +361,7 @@
     }
 }
 
-#define CLAMP(v, d, u) ((v) < (d)) ? (d) : (((v) >= (u)) ? (u) : (v))
+#define CLAMP(v, d, u) ((v) <= (d)) ? (d) : (((v) >= (u)) ? (u) : (v))
 
 void gpu_render_rect(psx_gpu_t* gpu, rect_data_t data) {
     if ((data.v0.x >= 1024) || (data.v0.y >= 512))
@@ -420,35 +426,39 @@
                 if (transp)
                     transp = (texel & 0x8000) != 0;
 
-                int tr = ((texel >> 0 ) & 0x1f) << 3;
-                int tg = ((texel >> 5 ) & 0x1f) << 3;
-                int tb = ((texel >> 10) & 0x1f) << 3;
+                float tr = ((texel >> 0 ) & 0x1f) << 3;
+                float tg = ((texel >> 5 ) & 0x1f) << 3;
+                float tb = ((texel >> 10) & 0x1f) << 3;
 
-                int mr = (data.v0.c >> 0 ) & 0xff;
-                int mg = (data.v0.c >> 8 ) & 0xff;
-                int mb = (data.v0.c >> 16) & 0xff;
+                float mr = (data.v0.c >> 0 ) & 0xff;
+                float mg = (data.v0.c >> 8 ) & 0xff;
+                float mb = (data.v0.c >> 16) & 0xff;
 
-                int cr = (tr * mr) / 0x80;
-                int cg = (tg * mg) / 0x80;
-                int cb = (tb * mb) / 0x80;
+                float cr = (tr * mr) / 128.0f;
+                float cg = (tg * mg) / 128.0f;
+                float cb = (tb * mb) / 128.0f;
 
-                uint32_t rgb = cr | (cg << 8) | (cb << 16);
+                unsigned int ucr = cr;
+                unsigned int ucg = cg;
+                unsigned int ucb = cb;
 
+                uint32_t rgb = ucr | (ucg << 8) | (ucb << 16);
+
                 color = BGR555(rgb);
             } else {
                 color = BGR555(data.v0.c);
             }
 
-            int cr = ((color >> 0 ) & 0x1f) << 3;
-            int cg = ((color >> 5 ) & 0x1f) << 3;
-            int cb = ((color >> 10) & 0x1f) << 3;
+            float cr = ((color >> 0 ) & 0x1f) << 3;
+            float cg = ((color >> 5 ) & 0x1f) << 3;
+            float cb = ((color >> 10) & 0x1f) << 3;
 
             if (transp) {
                 uint16_t back = gpu->vram[x + (y * 1024)];
 
-                int br = ((back >> 0 ) & 0x1f) << 3;
-                int bg = ((back >> 5 ) & 0x1f) << 3;
-                int bb = ((back >> 10) & 0x1f) << 3;
+                float br = ((back >> 0 ) & 0x1f) << 3;
+                float bg = ((back >> 5 ) & 0x1f) << 3;
+                float bb = ((back >> 10) & 0x1f) << 3;
 
                 switch (transp_mode) {
                     case 0: {
@@ -473,12 +483,16 @@
                     } break;
                 }
 
-                cr = (cr >= 0xff) ? 0xff : ((cr <= 0) ? 0 : cr);
-                cg = (cg >= 0xff) ? 0xff : ((cg <= 0) ? 0 : cg);
-                cb = (cb >= 0xff) ? 0xff : ((cb <= 0) ? 0 : cb);
+                cr = (cr >= 255.0f) ? 255.0f : ((cr <= 0.0f) ? 0.0f : cr);
+                cg = (cg >= 255.0f) ? 255.0f : ((cg <= 0.0f) ? 0.0f : cg);
+                cb = (cb >= 255.0f) ? 255.0f : ((cb <= 0.0f) ? 0.0f : cb);
 
-                uint32_t rgb = cr | (cg << 8) | (cb << 16);
+                unsigned int ucr = cr;
+                unsigned int ucg = cg;
+                unsigned int ucb = cb;
 
+                uint32_t rgb = ucr | (ucg << 8) | (ucb << 16);
+
                 color = BGR555(rgb);
             }
 
@@ -901,10 +915,10 @@
                 poly.v[3].ty = (gpu->buf[2+3*texc_offset] >> 8) & 0xff;
 
                 if (poly.attrib & PA_QUAD) {
-                    gpu_render_triangle(gpu, poly.v[0], poly.v[1], poly.v[2], poly);
-                    gpu_render_triangle(gpu, poly.v[1], poly.v[2], poly.v[3], poly);
+                    gpu_render_triangle(gpu, poly.v[0], poly.v[1], poly.v[2], poly, 0);
+                    gpu_render_triangle(gpu, poly.v[1], poly.v[2], poly.v[3], poly, 1);
                 } else {
-                    gpu_render_triangle(gpu, poly.v[0], poly.v[1], poly.v[2], poly);
+                    gpu_render_triangle(gpu, poly.v[0], poly.v[1], poly.v[2], poly, 0);
                 }
 
                 gpu->state = GPU_STATE_RECV_CMD;
@@ -1483,6 +1497,17 @@
 
                 uint16_t color = BGR555(gpu->color);
 
+                // printf("02 draw=(%u,%u-%u,%u) v0=(%u,%u) siz=(%u,%u)\n",
+                //     gpu->draw_x1,
+                //     gpu->draw_y1,
+                //     gpu->draw_x2,
+                //     gpu->draw_y2,
+                //     gpu->v0.x,
+                //     gpu->v0.y,
+                //     gpu->xsiz,
+                //     gpu->ysiz
+                // );
+
                 for (uint32_t y = gpu->v0.y; y < (gpu->v0.y + gpu->ysiz); y++) {
                     for (uint32_t x = gpu->v0.x; x < (gpu->v0.x + gpu->xsiz); x++) {
                         int bc = (x >= gpu->draw_x1) && (x <= gpu->draw_x2) &&
@@ -1597,10 +1622,10 @@
             gpu->texp_d = (gpu->gpustat >> 7) & 0x3;
         } break;
         case 0xe2: {
-            gpu->texw_mx = (gpu->buf[0] >> 0 ) & 0x1f;
-            gpu->texw_my = (gpu->buf[0] >> 5 ) & 0x1f;
-            gpu->texw_ox = (gpu->buf[0] >> 10) & 0x1f;
-            gpu->texw_oy = (gpu->buf[0] >> 15) & 0x1f;
+            gpu->texw_mx = ((gpu->buf[0] >> 0 ) & 0x1f) << 3;
+            gpu->texw_my = ((gpu->buf[0] >> 5 ) & 0x1f) << 3;
+            gpu->texw_ox = ((gpu->buf[0] >> 10) & 0x1f) << 3;
+            gpu->texw_oy = ((gpu->buf[0] >> 15) & 0x1f) << 3;
         } break;
         case 0xe3: {
             gpu->draw_x1 = (gpu->buf[0] >> 0 ) & 0x3ff;
--- a/psx/dev/input.h
+++ b/psx/dev/input.h
@@ -9,9 +9,9 @@
 
 typedef void (*psx_input_write_t)(void*, uint16_t);
 typedef uint32_t (*psx_input_read_t)(void*);
-typedef void (*psx_input_on_button_press_t)(void*, uint16_t);
-typedef void (*psx_input_on_button_release_t)(void*, uint16_t);
-typedef void (*psx_input_on_analog_change_t)(void*, uint16_t);
+typedef void (*psx_input_on_button_press_t)(void*, uint32_t);
+typedef void (*psx_input_on_button_release_t)(void*, uint32_t);
+typedef void (*psx_input_on_analog_change_t)(void*, uint32_t, uint8_t);
 typedef int (*psx_input_query_fifo_t)(void*);
 
 struct psx_input_t {
--- a/psx/dev/pad.c
+++ b/psx/dev/pad.c
@@ -6,19 +6,21 @@
 #include "../log.h"
 
 uint32_t pad_read_rx(psx_pad_t* pad) {
-    psx_input_t* joy = pad->joy_slot[(pad->ctrl >> 13) & 1];
-    psx_mcd_t* mcd = pad->mcd_slot[(pad->ctrl >> 13) & 1];
+    int slot = (pad->ctrl >> 13) & 1;
 
-    if (!pad->dest)
+    psx_input_t* joy = pad->joy_slot[slot];
+    psx_mcd_t* mcd = pad->mcd_slot[slot];
+
+    if (!pad->dest[slot])
         return 0xffffffff;
 
     if (!(pad->ctrl & CTRL_JOUT) && !(pad->ctrl & CTRL_RXEN))
         return 0xffffffff;
 
-    switch (pad->dest) {
+    switch (pad->dest[slot]) {
         case DEST_JOY: {
             if (!joy) {
-                pad->dest = 0;
+                pad->dest[slot] = 0;
 
                 return 0xffffffff;
             }
@@ -26,14 +28,18 @@
             uint8_t data = joy->read_func(joy->udata);
 
             if (!joy->query_fifo_func(joy->udata))
-                pad->dest = 0;
+                pad->dest[slot] = 0;
 
             return data;
         } break;
 
         case DEST_MCD: {
+            pad->dest[slot] = 0;
+
+            return 0xff;
+
             if (!mcd) {
-                pad->dest = 0;
+                pad->dest[slot] = 0;
 
                 return 0xffffffff;
             }
@@ -41,7 +47,7 @@
             uint8_t data = psx_mcd_read(mcd);
 
             if (!psx_mcd_query(mcd))
-                pad->dest = 0;
+                pad->dest[slot] = 0;
 
             return data;
         } break;
@@ -51,24 +57,35 @@
 }
 
 void pad_write_tx(psx_pad_t* pad, uint16_t data) {
-    psx_input_t* joy = pad->joy_slot[(pad->ctrl >> 13) & 1];
-    psx_mcd_t* mcd = pad->mcd_slot[(pad->ctrl >> 13) & 1];
+    int slot = (pad->ctrl >> 13) & 1;
+    // printf("slot=%u dest=%02x write tx data=%02x ", slot, pad->dest[slot], data);
 
+    psx_input_t* joy = pad->joy_slot[slot];
+    psx_mcd_t* mcd = pad->mcd_slot[slot];
+
     if (!(pad->ctrl & CTRL_TXEN))
         return;
 
-    if (!pad->dest) {
+    // if (slot == 0)
+    //     printf("slot %u (%02x) write %08x\n", slot, pad->dest[slot], data);
+
+    if (!pad->dest[slot]) {
         if ((data == DEST_JOY) || (data == DEST_MCD)) {
-            pad->dest = data;
-        
+            pad->dest[slot] = data;
+
+            // printf("  slot=%u dest=%02x\n", slot, data);
+
+            // if (slot == 0)
+            //     printf("dest=%02x\n", data);
+
             if (pad->ctrl & CTRL_ACIE)
                 pad->cycles_until_irq = 1500;
         }
     } else {
-        switch (pad->dest) {
+        switch (pad->dest[slot]) {
             case DEST_JOY: {
                 if (!joy) {
-                    pad->dest = 0;
+                    pad->dest[slot] = 0;
 
                     return;
                 }
@@ -76,12 +93,16 @@
                 joy->write_func(joy->udata, data);
 
                 if (!joy->query_fifo_func(joy->udata))
-                    pad->dest = 0;
+                    pad->dest[slot] = 0;
             } break;
 
             case DEST_MCD: {
+                pad->dest[slot] = 0;
+
+                return;
+
                 if (!mcd) {
-                    pad->dest = 0;
+                    pad->dest[slot] = 0;
 
                     return;
                 }
@@ -89,7 +110,7 @@
                 psx_mcd_write(mcd, data);
 
                 if (!psx_mcd_query(mcd))
-                    pad->dest = 0;
+                    pad->dest[slot] = 0;
             } break;
         }
 
@@ -99,16 +120,16 @@
 }
 
 uint32_t pad_handle_stat_read(psx_pad_t* pad) {
-    // log_set_quiet(0);
-    // log_fatal("pad stat read");
-    // log_set_quiet(1);
-    return 0x07;
+    // // log_set_quiet(0);
+    // printf("pad stat read\n");
+    // // log_set_quiet(1);
+    // return 0x07;
     psx_input_t* joy = pad->joy_slot[(pad->ctrl >> 13) & 1];
 
     if (!joy)
-        return 0x5 | pad->stat;
+        return 0xff;
 
-    return 0x5 | (joy->query_fifo_func(joy->udata) << 1);
+    return 0x7;
 }
 
 void pad_handle_ctrl_write(psx_pad_t* pad, uint32_t value) {
@@ -115,7 +136,8 @@
     pad->ctrl = value;
 
     if (!(pad->ctrl & CTRL_JOUT)) {
-        pad->dest = 0;
+        // pad->dest[0] = 0;
+        // pad->dest[1] = 0;
         pad->ctrl &= ~CTRL_SLOT;
 
         psx_mcd_reset(pad->mcd_slot[(pad->ctrl >> 13) & 1]);
@@ -210,7 +232,7 @@
     printf("Unhandled 8-bit PAD write at offset %08x (%02x)", offset, value);
 }
 
-void psx_pad_button_press(psx_pad_t* pad, int slot, uint16_t data) {
+void psx_pad_button_press(psx_pad_t* pad, int slot, uint32_t data) {
     psx_input_t* selected_slot = pad->joy_slot[slot];
 
     if (selected_slot)
@@ -217,11 +239,18 @@
         selected_slot->on_button_press_func(selected_slot->udata, data);
 }
 
-void psx_pad_button_release(psx_pad_t* pad, int slot, uint16_t data) {
+void psx_pad_button_release(psx_pad_t* pad, int slot, uint32_t data) {
     psx_input_t* selected_slot = pad->joy_slot[slot];
 
     if (selected_slot)
         selected_slot->on_button_release_func(selected_slot->udata, data);
+}
+
+void psx_pad_analog_change(psx_pad_t* pad, int slot, uint32_t stick, uint8_t data) {
+    psx_input_t* selected_slot = pad->joy_slot[slot];
+
+    if (selected_slot)
+        selected_slot->on_analog_change_func(selected_slot->udata, stick, data);
 }
 
 void psx_pad_attach_joy(psx_pad_t* pad, int slot, psx_input_t* input) {
--- a/psx/dev/pad.h
+++ b/psx/dev/pad.h
@@ -13,28 +13,6 @@
 #define PSX_PAD_SIZE  0x10
 #define PSX_PAD_END   0x1f80104f
 
-// Controller/Input IDs
-#define PSXI_ID_SD          0x5a41
-#define PSXI_ID_SA_PAD      0x5a73
-#define PSXI_ID_SA_STICK    0x5a53
-
-#define PSXI_SW_SDA_SELECT      0x0001
-#define PSXI_SW_SDA_L3          0x0002
-#define PSXI_SW_SDA_R3          0x0004
-#define PSXI_SW_SDA_START       0x0008
-#define PSXI_SW_SDA_PAD_UP      0x0010
-#define PSXI_SW_SDA_PAD_RIGHT   0x0020
-#define PSXI_SW_SDA_PAD_DOWN    0x0040
-#define PSXI_SW_SDA_PAD_LEFT    0x0080
-#define PSXI_SW_SDA_L2          0x0100
-#define PSXI_SW_SDA_R2          0x0200
-#define PSXI_SW_SDA_L1          0x0400
-#define PSXI_SW_SDA_R1          0x0800
-#define PSXI_SW_SDA_TRIANGLE    0x1000
-#define PSXI_SW_SDA_CIRCLE      0x2000
-#define PSXI_SW_SDA_CROSS       0x4000
-#define PSXI_SW_SDA_SQUARE      0x8000
-
 /*
   0     TX Ready Flag 1   (1=Ready/Started)
   1     RX FIFO Not Empty (0=Empty, 1=Not Empty)
@@ -136,7 +114,7 @@
     int enable_once;
     int cycles_until_irq;
     int cycle_counter;
-    int dest;
+    int dest[2];
 
     uint16_t mode, ctrl, baud, stat;
 } psx_pad_t;
@@ -150,8 +128,9 @@
 void psx_pad_write16(psx_pad_t*, uint32_t, uint16_t);
 void psx_pad_write8(psx_pad_t*, uint32_t, uint8_t);
 void psx_pad_destroy(psx_pad_t*);
-void psx_pad_button_press(psx_pad_t*, int, uint16_t);
-void psx_pad_button_release(psx_pad_t*, int, uint16_t);
+void psx_pad_button_press(psx_pad_t*, int, uint32_t);
+void psx_pad_button_release(psx_pad_t*, int, uint32_t);
+void psx_pad_analog_change(psx_pad_t*, int, uint32_t, uint8_t);
 void psx_pad_attach_joy(psx_pad_t*, int, psx_input_t*);
 void psx_pad_detach_joy(psx_pad_t*, int);
 void psx_pad_attach_mcd(psx_pad_t*, int, const char*);
--- a/psx/dev/spu.c
+++ b/psx/dev/spu.c
@@ -260,6 +260,8 @@
         return;
     }
 
+    adsr_calculate_values(spu, v);
+
     LEVEL += LEVEL_STEP;
 
     switch (spu->data[v].adsr_phase) {
@@ -319,8 +321,8 @@
             spu->data[i].playing = 1;
             spu->data[i].current_addr = spu->voice[i].adsaddr << 3;
             spu->data[i].repeat_addr = spu->data[i].current_addr;
-            spu->data[i].lvol = (float)(spu->voice[i].volumel << 1) / (float)0x7fff;
-            spu->data[i].rvol = (float)(spu->voice[i].volumer << 1) / (float)0x7fff;
+            spu->data[i].lvol = ((float)(spu->voice[i].volumel) / 32767.0f) * 2.0f;
+            spu->data[i].rvol = ((float)(spu->voice[i].volumer) / 32767.0f) * 2.0f;
             spu->data[i].adsr_sustain_level = ((spu->voice[i].envctl1 & 0xf) + 1) * 0x800;
             spu->data[i].envctl = (((uint32_t)spu->voice[i].envctl2) << 16) |
                                     (uint32_t)spu->voice[i].envctl1;
@@ -335,7 +337,7 @@
 
 void spu_koff(psx_spu_t* spu, uint32_t value) {
     for (int i = 0; i < VOICE_COUNT; i++)
-        if ((value & (1 << i)))
+        if (value & (1 << i))
             adsr_load_release(spu, i);
 }
 
@@ -353,6 +355,9 @@
         case SPUR_KOFFL: case SPUR_KOFFH: {
             int high = (offset & 2) != 0;
 
+            if (!value)
+                return 1;
+
             spu_koff(spu, value << (16 * high));
         } return 1;
 
@@ -423,7 +428,7 @@
 }
 
 void psx_spu_write8(psx_spu_t* spu, uint32_t offset, uint8_t value) {
-    log_fatal("Unhandled 8-bit SPU write at offset %08x (%02x)", offset, value);
+    printf("Unhandled 8-bit SPU write at offset %08x (%02x)\n", offset, value);
 }
 
 void psx_spu_destroy(psx_spu_t* spu) {
@@ -572,14 +577,11 @@
                 case 3: {
                     spu->endx |= (1 << v);
                     spu->data[v].current_addr = spu->data[v].repeat_addr;
-
-                    adsr_load_release(spu, v);
                 } break;
             }
 
-            if (spu->data[v].block_flags & 4) {
+            if (spu->data[v].block_flags & 4)
                 spu->data[v].repeat_addr = spu->data[v].current_addr;
-            }
 
             spu_read_block(spu, v);
         }
--- a/psx/input/sda.c
+++ b/psx/input/sda.c
@@ -19,9 +19,15 @@
 
     sda->tx_data = 0xff;
     sda->tx_data_ready = 1;
+    sda->prev_model = model;
     sda->model = model;
     sda->state = SDA_STATE_TX_HIZ;
     sda->sw = 0xffff;
+    sda->sa_mode = 0;
+    sda->adc0 = 0x80;
+    sda->adc1 = 0x80;
+    sda->adc2 = 0x80;
+    sda->adc3 = 0x80;
 }
 
 uint32_t psxi_sda_read(void* udata) {
@@ -33,15 +39,38 @@
         case SDA_STATE_TX_IDH: sda->tx_data = 0x5a; break;
         case SDA_STATE_TX_SWL: sda->tx_data = sda->sw & 0xff; break;
 
-        // Last state
+        // Digital pad stops sending data here
         case SDA_STATE_TX_SWH: {
-            sda->tx_data_ready = 0;
-            sda->state = 0;
+            if (sda->sa_mode == SA_MODE_ANALOG) {
+                sda->tx_data_ready = 1;
+                sda->state = SDA_STATE_TX_ADC0;
+            } else {
+                sda->tx_data_ready = 0;
+                sda->state = SDA_STATE_TX_HIZ;
+            }
 
             return sda->sw >> 8;
         } break;
+
+        case SDA_STATE_TX_ADC0: sda->tx_data = sda->adc0; break;
+        case SDA_STATE_TX_ADC1: sda->tx_data = sda->adc1; break;
+        case SDA_STATE_TX_ADC2: sda->tx_data = sda->adc2; break;
+
+        // Analog pad stops sending data here
+        case SDA_STATE_TX_ADC3: {
+            sda->tx_data_ready = 0;
+            sda->state = SDA_STATE_TX_HIZ;
+
+            if (sda->model == 0xf3)
+                sda->model = sda->prev_model;
+
+            return sda->adc3;
+        } break;
+        
     }
 
+    // printf("  sda read %u -> %02x\n", sda->state, sda->tx_data);
+
     sda->tx_data_ready = 1;
     sda->state++;
 
@@ -49,18 +78,47 @@
 }
 
 void psxi_sda_write(void* udata, uint16_t data) {
+    psxi_sda_t* sda = (psxi_sda_t*)udata;
+
     // To-do: Handle TAP and MOT bytes here
+    if (data == 0x01) {
+        sda->tx_data = 0xff;
+        sda->tx_data_ready = 1;
+        sda->state = SDA_STATE_TX_HIZ;
+    }
 
+    if (data == 0x43) {
+        if (sda->sa_mode == SA_MODE_ANALOG) {
+            sda->prev_model = sda->model;
+            sda->model = 0xf3;
+        } else {
+            sda->tx_data = 0xff;
+            sda->tx_data_ready = 0;
+            sda->state = SDA_STATE_TX_HIZ;
+        }
+    }
+
     return;
 }
 
-void psxi_sda_on_button_press(void* udata, uint16_t data) {
+void psxi_sda_on_button_press(void* udata, uint32_t data) {
     psxi_sda_t* sda = (psxi_sda_t*)udata;
 
+    if (data == PSXI_SW_SDA_ANALOG) {
+        sda->sa_mode ^= 1;
+
+        sda->prev_model = sda->model;
+        sda->model = sda->sa_mode ? SDA_MODEL_ANALOG_STICK : sda->prev_model;
+
+        printf("sda: Switched to %s mode\n", sda->sa_mode ? "analog" : "digital");
+
+        return;
+    }
+
     sda->sw &= ~data;
 }
 
-void psxi_sda_on_button_release(void* udata, uint16_t data) {
+void psxi_sda_on_button_release(void* udata, uint32_t data) {
     psxi_sda_t* sda = (psxi_sda_t*)udata;
 
     sda->sw |= data;
@@ -67,9 +125,16 @@
 }
 
 // To-do: Implement analog mode
-void psxi_sda_on_analog_change(void* udata, uint16_t data) {
+void psxi_sda_on_analog_change(void* udata, uint32_t axis, uint8_t data) {
     // Suppress warning until we implement analog mode
-    // psxi_sda_t* sda = (psxi_sda_t*)udata;
+    psxi_sda_t* sda = (psxi_sda_t*)udata;
+
+    switch (axis) {
+        case PSXI_AX_SDA_RIGHT_HORZ: sda->adc0 = data; break;
+        case PSXI_AX_SDA_RIGHT_VERT: sda->adc1 = data; break;
+        case PSXI_AX_SDA_LEFT_HORZ: sda->adc2 = data; break;
+        case PSXI_AX_SDA_LEFT_VERT: sda->adc3 = data; break;
+    }
 }
 
 int psxi_sda_query_fifo(void* udata) {
--- a/psx/input/sda.h
+++ b/psx/input/sda.h
@@ -14,22 +14,27 @@
 #define SDA_MODEL_ANALOG_PAD    0x73
 #define SDA_MODEL_ANALOG_STICK  0x53
 
-#define PSXI_SW_SDA_SELECT      0x0001
-#define PSXI_SW_SDA_L3          0x0002
-#define PSXI_SW_SDA_R3          0x0004
-#define PSXI_SW_SDA_START       0x0008
-#define PSXI_SW_SDA_PAD_UP      0x0010
-#define PSXI_SW_SDA_PAD_RIGHT   0x0020
-#define PSXI_SW_SDA_PAD_DOWN    0x0040
-#define PSXI_SW_SDA_PAD_LEFT    0x0080
-#define PSXI_SW_SDA_L2          0x0100
-#define PSXI_SW_SDA_R2          0x0200
-#define PSXI_SW_SDA_L1          0x0400
-#define PSXI_SW_SDA_R1          0x0800
-#define PSXI_SW_SDA_TRIANGLE    0x1000
-#define PSXI_SW_SDA_CIRCLE      0x2000
-#define PSXI_SW_SDA_CROSS       0x4000
-#define PSXI_SW_SDA_SQUARE      0x8000
+#define PSXI_SW_SDA_SELECT      0x00000001
+#define PSXI_SW_SDA_L3          0x00000002
+#define PSXI_SW_SDA_R3          0x00000004
+#define PSXI_SW_SDA_START       0x00000008
+#define PSXI_SW_SDA_PAD_UP      0x00000010
+#define PSXI_SW_SDA_PAD_RIGHT   0x00000020
+#define PSXI_SW_SDA_PAD_DOWN    0x00000040
+#define PSXI_SW_SDA_PAD_LEFT    0x00000080
+#define PSXI_SW_SDA_L2          0x00000100
+#define PSXI_SW_SDA_R2          0x00000200
+#define PSXI_SW_SDA_L1          0x00000400
+#define PSXI_SW_SDA_R1          0x00000800
+#define PSXI_SW_SDA_TRIANGLE    0x00001000
+#define PSXI_SW_SDA_CIRCLE      0x00002000
+#define PSXI_SW_SDA_CROSS       0x00004000
+#define PSXI_SW_SDA_SQUARE      0x00008000
+#define PSXI_SW_SDA_ANALOG      0x00010000
+#define PSXI_AX_SDA_RIGHT_HORZ  0x00020000
+#define PSXI_AX_SDA_RIGHT_VERT  0x00030000
+#define PSXI_AX_SDA_LEFT_HORZ   0x00040000
+#define PSXI_AX_SDA_LEFT_VERT   0x00050000
 
 enum {
     SDA_STATE_TX_HIZ = 0,
@@ -36,7 +41,11 @@
     SDA_STATE_TX_IDL,
     SDA_STATE_TX_IDH,
     SDA_STATE_TX_SWL,
-    SDA_STATE_TX_SWH
+    SDA_STATE_TX_SWH,
+    SDA_STATE_TX_ADC0,
+    SDA_STATE_TX_ADC1,
+    SDA_STATE_TX_ADC2,
+    SDA_STATE_TX_ADC3
 };
 
 enum {
@@ -45,6 +54,7 @@
 };
 
 typedef struct {
+    uint8_t prev_model;
     uint8_t model;
     int state;
     int sa_mode;
@@ -51,6 +61,10 @@
     uint16_t sw;
     uint8_t tx_data;
     int tx_data_ready;
+    uint8_t adc0;
+    uint8_t adc1;
+    uint8_t adc2;
+    uint8_t adc3;
 } psxi_sda_t;
 
 psxi_sda_t* psxi_sda_create();
--- a/psx/psx.c
+++ b/psx/psx.c
@@ -1,4 +1,4 @@
-#include "psx/psx.h"
+#include "psx.h"
 
 psx_t* psx_create() {
     return (psx_t*)malloc(sizeof(psx_t));
--