diff --git a/src/ir_input.h b/src/ir_input.h
deleted file mode 100644
index f02bde8..0000000
--- a/src/ir_input.h
+++ /dev/null
@@ -1,672 +0,0 @@
-
-/* Copyright (c) 2010-2020, Peter Barrett
- **
- ** Permission to use, copy, modify, and/or distribute this software for
- ** any purpose with or without fee is hereby granted, provided that the
- ** above copyright notice and this permission notice appear in all copies.
- **
- ** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
- ** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
- ** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
- ** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
- ** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
- ** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
- ** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
- ** SOFTWARE.
- */
-
-#ifndef __ir_input__
-#define __ir_input__
-
-// generate events on state changes of IR pin, feed to state machines.
-// event timings  have a resolution of HSYNC, timing is close enough between 15720 and 15600 to make this work
-// poll to synthesize hid events at every frame
-// i know there is a perfectly good peripheral for this in the ESP32 but this seems more fun somehow
-
-#define WEBTV_KEYBOARD
-#define RETCON_CONTROLLER
-#define FLASHBACK_CONTROLLER
-//#define APPLE_TV_CONTROLLER
-
-uint8_t _ir_last = 0;
-uint8_t _ir_count = 0;
-uint8_t _keyDown = 0;
-uint8_t _keyUp = 0;
-
-void IRAM_ATTR ir_event(uint8_t ticks, uint8_t value); // t is HSYNCH ticks, v is value
-
-inline void IRAM_ATTR ir_sample()
-{
-    uint8_t ir = (GPIO.in & (1 << IR_PIN)) != 0;
-    if (ir != _ir_last)
-    {
-        ir_event(_ir_count,_ir_last);
-        _ir_count = 0;
-        _ir_last = ir;
-    }
-    if (_ir_count != 0xFF)
-        _ir_count++;
-}
-
-class IRState {
-public:
-    uint8_t _state = 0;
-    uint16_t _code = 0;
-    uint16_t _output = 0;
-    uint16_t _joy[2] = {0};
-    uint16_t _joy_last[2] = {0};
-    uint8_t _timer[2] = {0};
-
-    void set(int i, int m, int t)
-    {
-        uint8_t b = 0;
-        if ((m & (GENERIC_LEFT | GENERIC_RIGHT)) == (GENERIC_LEFT | GENERIC_RIGHT))
-            b++; // bogus?
-        if ((m & (GENERIC_UP | GENERIC_DOWN)) == (GENERIC_UP | GENERIC_DOWN))
-            b++; // bogus?
-        if (b) {
-            printf("bogus:%04X\n",m);
-            return;
-        }
-
-        _joy[i] = m;
-        _timer[i] = t;
-    }
-
-    // make a fake hid event
-    int get_hid(uint8_t* dst)
-    {
-        if (_timer[0] && !--_timer[0])
-            _joy[0] = 0;
-        if (_timer[1] && !--_timer[1])
-            _joy[1] = 0;
-        if ((_joy[0] != _joy_last[0]) || (_joy[1] != _joy_last[1])) {
-            _joy_last[0] = _joy[0];
-            _joy_last[1] = _joy[1];
-            dst[0] = 0xA1;
-            dst[1] = 0x42;
-            dst[2] = _joy[0];
-            dst[3] = _joy[0]>>8;
-            dst[4] = _joy[1];
-            dst[5] = _joy[1]>>8;
-            return 6;           // only sent if it changes
-        }
-        return 0;
-    }
-};
-
-//==========================================================
-//==========================================================
-// Apple remote NEC code
-// pretty easy to adapt to any NEC remote
-
-#ifdef APPLE_TV_CONTROLLER
-
-// Silver apple remote, 7 Bit code
-// should work with both silvers and white
-#define APPLE_MENU      0x40
-#define APPLE_PLAY      0x7A
-#define APPLE_CENTER    0x3A
-#define APPLE_RIGHT     0x60
-#define APPLE_LEFT      0x10
-#define APPLE_UP        0x50
-#define APPLE_DOWN      0x30
-
-#define APPLE_RELEASE   0x20 // sent after menu and play?
-
-//    generic repeat code
-#define NEC_REPEAT    0xAAAA
-
-/*
-  9ms preamble ~142
-  4.5ms 1 ~71 - start
-  2.25ms ~35 - repeat
-
-  32 bits
-  0 – a 562.5µs/562.5µs   9ish wide
-  1 – a 562.5µs/1.6875ms  27ish wide
-*/
-
-IRState _apple;
-int get_hid_apple(uint8_t* dst)
-{
-    if (_apple._output)
-    {
-        if (_apple._output != NEC_REPEAT)
-            _keyDown = (_apple._output >> 8) & 0x7F;  // 7 bit code
-        _apple._output = 0;
-
-        uint16_t k = 0;
-        switch (_keyDown) {
-            case APPLE_UP:     k = GENERIC_UP;     break;
-            case APPLE_DOWN:   k = GENERIC_DOWN;   break;
-            case APPLE_LEFT:   k = GENERIC_LEFT;   break;
-            case APPLE_RIGHT:  k = GENERIC_RIGHT;  break;
-            case APPLE_CENTER: k = GENERIC_FIRE;   break;
-            case APPLE_MENU:   k = GENERIC_RESET;  break;
-            case APPLE_PLAY:   k = GENERIC_SELECT; break;
-        }
-        _apple.set(0,k,15); // 108ms repeat period
-    }
-    return _apple.get_hid(dst);
-}
-
-// NEC codes used by apple remote
-void IRAM_ATTR ir_apple(uint8_t t, uint8_t v)
-{
-  if (!v) {
-    if (t > 32)
-      _apple._state = 0;
-  } else {
-    if (t < 32)
-    {
-      _apple._code <<= 1;
-      if (t >= 12)
-        _apple._code |= 1;
-      if (++_apple._state == 32)
-        _apple._output = _apple._code;    // Apple code in bits 14-8*
-    } else {
-        if (t > 32 && t < 40 && !_apple._state)  // Repeat 2.25ms pulse 4.5ms start
-          _apple._output = NEC_REPEAT;
-        _apple._state = 0;
-    }
-  }
-}
-
-#endif
-
-//==========================================================
-//==========================================================
-//  Atari Flashback 4 wireless controllers
-
-#ifdef FLASHBACK_CONTROLLER
-
-// HSYNCH period is 44/315*455 or 63.55555..us
-// 18 bit code 1.87khz clock
-// 2.3ms zero preamble  // 36
-// 0 is 0.27ms pulse    // 4
-// 1 is 0.80ms pulse    // 13
-
-// Keycodes...
-// Leading bit is 1 for player 1 control..
-
-#define PREAMBLE(_t) (_t >= 34 && _t <= 38)
-#define SHORT(_t)    (_t >= 2 && _t <= 6)
-#define LONG(_t)     (_t >= 11 && _t <= 15)
-
-// Codes come in pairs 33ms apart
-// Sequence repeats every 133ms
-// bitmap is released if no code for 10 vbls (167ms) or 0x01 (p1) / 0x0F (p2)
-// up to 12 button bits, 4 bits of csum/p1/p2 indication
-
-//  called at every loop ~60Hz
-
-IRState _flashback;
-int get_hid_flashback(uint8_t* dst)
-{
-    if (_flashback._output)
-    {
-        uint16_t m = _flashback._output >> 4;        // 12 bits of buttons
-        printf("F:%04X\n",m);
-        uint8_t csum = _flashback._output & 0xF;     // csum+1 for p1, csum-1 for p2
-        uint8_t s = m + (m >> 4) + (m >> 8);
-        if (((s+1) & 0xF) == csum)
-            _flashback.set(0,m,15);
-        else if (((s-1) & 0xF) == csum)
-            _flashback.set(1,m,20);
-        _flashback._output = 0;
-    }
-    return _flashback.get_hid(dst);
-}
-
-void IRAM_ATTR ir_flashback(uint8_t t, uint8_t v)
-{
-  if (_flashback._state == 0)
-  {
-    if (PREAMBLE(t) && (v == 0))  // long 0, rising edge of start bit
-    {
-      _flashback._state = 1;
-    }
-  }
-  else
-  {
-    if (v)
-    {
-      _flashback._code <<= 1;
-      if (LONG(t))
-      {
-        _flashback._code |= 1;
-      }
-      else if (!SHORT(t))
-      {
-        _flashback._state = 0;  // framing error
-        return;
-      }
-
-      if (++_flashback._state == 19)
-      {
-        _flashback._output = _flashback._code;
-        _flashback._state = 0;
-      }
-    }
-    else
-    {
-      if (!SHORT(t))
-        _flashback._state = 0;  // Framing err
-    }
-  }
-}
-
-#endif
-
-//==========================================================
-//==========================================================
-// RETCON controllers
-// 75ms keyboard repeat
-// Preamble is 0.80ms low, 0.5 high
-// Low: 0.57ms=0,0.27,s=1, high 0.37
-// 16 bits
-// Preamble = 800,500/63.55555 ~ 12.6,7.87
-// LOW0 = 8.97
-// LOW1 = 4.25
-// HIGH = 5.82
-
-#ifdef RETCON_CONTROLLER
-
-// number of 63.55555 cycles per bit
-#define PREAMBLE_L(_t) (_t >= 12 && _t <= 14) // 12/13/14 preamble
-#define PREAMBLE_H(_t) (_t >= 6 && _t <= 10)  // 8
-#define LOW_0(_t)     (_t >= 8 && _t <= 10)   // 8/9/10
-#define LOW_1(_t)     (_t >= 4 && _t <= 6)    // 4/5/6
-
-// map retcon to generic
-const uint16_t _jmap[] = {
-  0x0400, GENERIC_UP,
-  0x0200, GENERIC_DOWN,
-  0x0100, GENERIC_LEFT,
-  0x0080, GENERIC_RIGHT,
-
-  0x1000, GENERIC_SELECT,
-  0x0800, GENERIC_START,
-
-  0x0020, GENERIC_FIRE_X,
-  0x0040, GENERIC_FIRE_Y,
-  0x0002, GENERIC_FIRE_Z,
-
-  0x2000, GENERIC_FIRE_A,
-  0x4000, GENERIC_FIRE_B,
-  0x0008, GENERIC_FIRE_C,
-};
-
-IRState _retcon;
-int get_hid_retcon(uint8_t* dst)
-{
-    if (_retcon._output)
-    {
-        uint16_t m = 0;
-        const uint16_t* jmap = _jmap;
-        int16_t i = 12;
-        uint16_t k = _retcon._output;
-        _retcon._output = 0;
-        while (i--)
-        {
-            if (jmap[0] & k)
-                m |= jmap[1];
-            jmap += 2;
-        }
-        printf("R:%04X\n",m);
-        _retcon.set(k >> 15,m,20);
-    }
-    return _retcon.get_hid(dst);
-}
-
-void IRAM_ATTR ir_retcon(uint8_t t, uint8_t v)
-{
-  if (_retcon._state == 0)
-  {
-    if (v == 0)  {   // start bit
-      if (PREAMBLE_L(t))
-        _retcon._state = 1;
-    }
-  }
-  else
-  {
-    if (!v)
-    {
-      _retcon._code <<= 1;
-      if (LOW_1(t))
-        _retcon._code |= 1;
-      if (_retcon._state++ == 16)
-      {
-        _retcon._output = _retcon._code;
-        _retcon._state = 0;
-      }
-    }
-  }
-}
-
-#endif
-
-
-//==========================================================
-//==========================================================
-//  Webtv keyboard
-#ifdef WEBTV_KEYBOARD
-
-#define BAUDB   12  // Width of uart bit in HSYNCH
-#define WT_PREAMBLE(_t) (_t >= 36 && _t <= 40)   // 3.25 baud
-#define SHORTBIT(_t) (_t >= 9 && _t <= 13)     // 1.5ms ish
-
-// converts webtv keyboard to common scancodes
-const uint8_t _ir2scancode[128] = {
-    0x00, // 00
-    0x00, // 02
-    0x05, // 04 B
-    0x00, // 06
-    0x00, // 08
-    0x51, // 0A Down
-    0x00, // 0C
-    0x00, // 0E
-    0x00, // 10
-    0x50, // 12 Left
-    0xE6, // 14 Right Alt
-    0x38, // 16 /
-    0xE2, // 18 Left Alt
-    0x4F, // 1A Right
-    0x2C, // 1C Space
-    0x11, // 1E N
-    0x32, // 20 #
-    0x00, // 22
-    0x22, // 24 5
-    0x41, // 26 F8
-    0x3B, // 28 F2
-    0xE4, // 2A Right Ctrl
-    0x00, // 2C
-    0x2E, // 2E =
-    0x3A, // 30 F1
-    0x4A, // 32 Home
-    0x00, // 34
-    0x2D, // 36 -
-    0xE0, // 38 Left Ctrl
-    0x35, // 3A `
-    0x42, // 3C F9
-    0x23, // 3E 6
-    0x00, // 40
-    0x00, // 42
-    0x19, // 44 V
-    0x37, // 46 .
-    0x06, // 48 C
-    0x68, // 4A F13
-    0xE5, // 4C Right Shift
-    0x36, // 4E ,
-    0x1B, // 50 X
-    0x4D, // 52 End
-    0x00, // 54
-    0x00, // 56
-    0x1D, // 58 Z
-    0x00, // 5A
-    0x28, // 5C Return
-    0x10, // 5E M
-    0x00, // 60
-    0xE7, // 62 Right GUI
-    0x09, // 64 F
-    0x0F, // 66 L
-    0x07, // 68 D
-    0x4E, // 6A PageDown
-    0x00, // 6C
-    0x0E, // 6E K
-    0x16, // 70 S
-    0x4B, // 72 PageUp
-    0x00, // 74
-    0x33, // 76 ;
-    0x04, // 78 A
-    0x00, // 7A
-    0x31, // 7C |
-    0x0D, // 7E J
-    0x00, // 80
-    0x00, // 82
-    0x17, // 84 T
-    0x40, // 86 F7
-    0x3C, // 88 F3
-    0x00, // 8A
-    0xE1, // 8C Left Shift
-    0x30, // 8E ]
-    0x39, // 90 CapsLock
-    0x00, // 92
-    0x29, // 94 Escape
-    0x2F, // 96 [
-    0x2B, // 98 Tab
-    0x00, // 9A
-    0x2A, // 9C Backspace
-    0x1C, // 9E Y
-    0x00, // A0
-    0x00, // A2
-    0x21, // A4 4
-    0x26, // A6 9
-    0x20, // A8 3
-    0x44, // AA F11
-    0x00, // AC
-    0x25, // AE 8
-    0x1F, // B0 2
-    0x00, // B2
-    0x46, // B4 PrintScreen
-    0x27, // B6 0
-    0x1E, // B8 1
-    0x45, // BA F12
-    0x43, // BC F10
-    0x24, // BE 7
-    0x00, // C0
-    0x00, // C2
-    0x0A, // C4 G
-    0x00, // C6
-    0x3D, // C8 F4
-    0x00, // CA
-    0x00, // CC
-    0x00, // CE
-    0x3E, // D0 F5
-    0x52, // D2 Up
-    0xE3, // D4 Left GUI
-    0x34, // D6 '
-    0x29, // D8 Escape
-    0x48, // DA Pause
-    0x3F, // DC F6
-    0x0B, // DE H
-    0x00, // E0
-    0x00, // E2
-    0x15, // E4 R
-    0x12, // E6 O
-    0x08, // E8 E
-    0x00, // EA
-    0x00, // EC
-    0x0C, // EE I
-    0x1A, // F0 W
-    0x00, // F2
-    0x53, // F4 Numlock
-    0x13, // F6 P
-    0x14, // F8 Q
-    0x00, // FA
-    0x00, // FC
-    0x18, // FE U
-};
-
-
-// IR Keyboard State
-uint8_t _state = 0;
-uint16_t _code = 0;
-uint8_t _wt_keys[6] = {0};
-uint8_t _wt_expire[6] = {0};
-uint8_t _wt_modifiers = 0;
-
-static
-uint8_t parity_check(uint8_t k)
-{
-    uint8_t c;
-    uint8_t v = k;
-    for (c = 0; v; c++)
-      v &= v-1;
-    return (c & 1) ? k : 0;
-}
-
-// make a mask from modifier keys
-static
-uint8_t ctrl_mask(uint8_t k)
-{
-    switch (k) {
-        case 0x38:  return KEY_MOD_LCTRL;
-        case 0x8C:  return KEY_MOD_LSHIFT;
-        case 0x18:  return KEY_MOD_LALT;
-        case 0xD4:  return KEY_MOD_LGUI;
-        case 0x2A:  return KEY_MOD_RCTRL;
-        case 0x4C:  return KEY_MOD_RSHIFT;
-        case 0x14:  return KEY_MOD_RALT;
-        case 0x62:  return KEY_MOD_RGUI;
-    }
-    return 0;
-}
-
-// update state of held keys
-// produce a hid keyboard record
-int get_hid_webtv(uint8_t* dst)
-{
-    bool dirty = false;
-    uint8_t k = parity_check(_keyUp);
-    _keyUp = 0;
-    if (k) {
-       _wt_modifiers &= ~ctrl_mask(k);
-        for (int i = 0; i < 6; i++) {
-            if (_wt_keys[i] == k) {
-                _wt_expire[i] = 1;  // key will expire this frame
-                break;
-            }
-        }
-    }
-
-    k = parity_check(_keyDown);
-    _keyDown = 0;
-    if (k) {
-        _wt_modifiers |= ctrl_mask(k);
-
-        // insert key into list of pressed keys
-        int j = 0;
-        for (int i = 0; i < 6; i++) {
-            if ((_wt_keys[i] == 0) || (_wt_expire[i] == 0) || (_wt_keys[i] == k)) {
-                j = i;
-                break;
-            }
-            if (_wt_expire[i] < _wt_expire[j])
-                j = i;
-        }
-        if (_wt_keys[j] != k) {
-            _wt_keys[j] = k;
-            dirty = true;
-        }
-        _wt_expire[j] = 8;  // key will be down for ~130ms or 8 frames
-    }
-
-    // generate hid keyboard events if anything was pressed or changed...
-    // A1 01 mods XX k k k k k k
-    dst[0] = 0xA1;
-    dst[1] = 0x01;
-    dst[2] = _wt_modifiers;
-    dst[3] = 0;
-    int j = 0;
-    for (int i = 0; i < 6; i++) {
-        dst[4+i] = 0;
-        if (_wt_expire[i]) {
-            if (!--_wt_expire[i])
-                dirty = true;
-        }
-        if (_wt_expire[i] == 0) {
-            _wt_keys[i] = 0;
-        } else {
-            dst[4 + j++] = _ir2scancode[_wt_keys[i] >> 1];
-        }
-    }
-    return dirty ? 10 : 0;
-}
-
-// WebTV UART like keyboard protocol
-// 3.25 bit 0 start preamble the 19 bits
-// 10 bit code for keyup, keydown, all keys released etc
-// 7 bit keycode + parity bit.
-//
-
-#define KEYDOWN     0x4A
-#define KEYUP       0x5E
-void IRAM_ATTR ir_webtv(uint8_t t, uint8_t v)
-{
-  if (_state == 0)
-  {
-    if (WT_PREAMBLE(t) && (v == 0))  // long 0, rising edge of start bit
-      _state = 1;
-  }
-  else if (_state == 1)
-  {
-    _state = (SHORTBIT(t) && (v == 1)) ? 2 : 0;
-  }
-  else
-  {
-      t += BAUDB>>1;
-      uint8_t bits = _state-2;
-      while ((t > BAUDB) && (bits < 16))
-      {
-          t -= BAUDB;
-          _code = (_code << 1) | v;
-          bits++;
-      }
-      if (bits == 16)
-      {
-        v = t <= BAUDB;
-        uint8_t md = _code >> 8;
-        _code |= v;                 // Low bit of code is is parity
-        if (md == KEYDOWN)
-            _keyDown = _code;
-        else if (md == KEYUP)
-            _keyUp = _code;
-        _state = 0;                 // got one
-        return;
-      }
-      _state = bits+2;
-    }
-}
-#endif
-
-// called from interrupt
-void IRAM_ATTR ir_event(uint8_t t, uint8_t v)
-{
-#ifdef WEBTV_KEYBOARD
-    ir_webtv(t,v);
-#endif
-#ifdef RETCON_CONTROLLER
-    ir_retcon(t,v);
-#endif
-#ifdef APPLE_TV_CONTROLLER
-    ir_apple(t,v);
-#endif
-#ifdef FLASHBACK_CONTROLLER
-    ir_flashback(t,v);
-#endif
-}
-
-// called every frame from emu
-int get_hid_ir(uint8_t* dst)
-{
-    int n = 0;
-#ifdef APPLE_TV_CONTROLLER
-    if (n = get_hid_apple(dst))
-        return n;
-#endif
-#ifdef RETCON_CONTROLLER
-    if (n = get_hid_retcon(dst))
-        return n;
-#endif
-#ifdef FLASHBACK_CONTROLLER
-    if (n = get_hid_flashback(dst))
-        return n;
-#endif
-    #ifdef WEBTV_KEYBOARD
-        return get_hid_webtv(dst);
-    #endif
-    return 0;
-}
-#endif
diff --git a/src/nintendo.h b/src/nintendo.h
new file mode 100644
index 0000000..ded6a51
--- /dev/null
+++ b/src/nintendo.h
@@ -0,0 +1,195 @@
+
+/*
+ ** Copyright (c) 2010-2020, Peter Barrett
+ ** Copyright (c) 2024, Michel Depeige
+ **
+ ** Permission to use, copy, modify, and/or distribute this software for
+ ** any purpose with or without fee is hereby granted, provided that the
+ ** above copyright notice and this permission notice appear in all copies.
+ **
+ ** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ ** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ ** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
+ ** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
+ ** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ ** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ ** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ ** SOFTWARE.
+ */
+
+#ifndef __NINTENDO_H__
+#define __NINTENDO_H__
+
+#undef NES_CONTROLLER
+#define SNES_CONTROLLER
+
+/*
+ * ^^ choose either NES or SNES above
+ *
+ * inspired from Peter's work, and mixed with code I had around + CornN64 commits:
+ * - 8bdbe1c0ff234d1721ffe48472712b58b7f3683f
+ * - 937a9567224ab981d41fef5ba0e9ddcf19758996
+ *
+ * code only uses ESP-IDF, no Arduinism
+ *
+ * ADBSRC switch set if the classic controller is player A (0, pulled down) or B (1)
+ * the yellow LED on pin 26 is used as a controller activity LED by calling esp-idf stuff directly
+ * pin 26 is DAC channel 2 but it isn't used nor enabled so it works
+ */
+
+#define GPIO_YELLOWLED	26
+#define GPIO_ADBSRC		32
+
+/* how many buttons to poll */
+#ifdef NES_CONTROLLER
+#define NC_CLOCKS 8
+#endif
+#ifdef SNES_CONTROLLER
+#define NC_CLOCKS 12
+#endif
+
+/*
+ * this is added because poking into ESP-IDF is too fast. We are not letting
+ * enough time for the controller shift register and/or the main loop in the menu
+ */
+#define NC_DELAY 8
+
+/* globals for controller memory */
+uint16_t	nc_joy[2] = {0};
+uint16_t	nc_joy_last[2] = {0};
+
+/* internal functions */
+static void nc_set(int i, int m) {
+        uint8_t b = 0;
+
+        /* do not store if directional pad combinaisons are impossible */
+        if ((m & (GENERIC_LEFT | GENERIC_RIGHT)) == (GENERIC_LEFT | GENERIC_RIGHT))
+            b++; // bogus?
+        if ((m & (GENERIC_UP | GENERIC_DOWN)) == (GENERIC_UP | GENERIC_DOWN))
+            b++; // bogus?
+        if (b) {
+            printf("bogus:%04X\n",m);
+            return;
+        }
+
+        nc_joy[i] = m;
+}
+
+static int get_hid_nc(uint8_t* dst) {
+	/* send only HID event if something changes */
+	if ((nc_joy[0] != nc_joy_last[0]) || (nc_joy[1] != nc_joy_last[1])) {
+		nc_joy_last[0] = nc_joy[0];
+		nc_joy_last[1] = nc_joy[1];
+		dst[0] = 0xA1;
+		dst[1] = 0x42;
+		dst[2] = nc_joy[0];
+		dst[3] = nc_joy[0] >> 8;
+		dst[4] = nc_joy[1];
+		dst[5] = nc_joy[1] >> 8;
+		return 6;
+	}
+	return 0;
+}
+
+/*************************************
+ * Classic hard wired NES controller *
+ *************************************/
+enum NES_button {
+	NES_A = 0,
+	NES_B = 1,
+	NES_SELECT = 2,
+	NES_START = 3,
+	NES_UP = 4,
+	NES_DOWN = 5,
+	NES_LEFT = 6,
+	NES_RIGHT = 7,
+};
+
+/***************************************
+ * Classic hard wired SNES controllers *
+ ***************************************/
+enum SNES_button {
+	SNES_B = 0,
+	SNES_Y = 1,
+	SNES_SELECT = 2,
+	SNES_START = 3,
+	SNES_UP = 4,
+	SNES_DOWN = 5,
+	SNES_LEFT = 6,
+	SNES_RIGHT = 7,
+	SNES_A = 8,
+	SNES_X = 9,
+	SNES_L = 10,
+	SNES_R = 11,
+};
+
+/* called every frame from emu */
+int IRAM_ATTR get_hid_nintendo(uint8_t* dst) {
+	uint16_t buttons = 0;
+	uint16_t k = 0;
+
+	gpio_set_level((gpio_num_t)NES_CTRL_LATCH, 1);
+	ets_delay_us(NC_DELAY);
+	gpio_set_level((gpio_num_t)NES_CTRL_LATCH, 0);
+	ets_delay_us(NC_DELAY);
+
+	for (int i = 0; i < NC_CLOCKS; i++) {
+		buttons |= gpio_get_level((gpio_num_t)NES_CTRL_DAT) << i;
+		gpio_set_level((gpio_num_t)NES_CTRL_CLK, 0);
+		ets_delay_us(NC_DELAY);
+		gpio_set_level((gpio_num_t)NES_CTRL_CLK, 1);
+		ets_delay_us(NC_DELAY);
+	}
+
+#ifdef SNES_CONTROLLER
+	/* on NES, menu is LEFT (directional pad) + SELECT */
+	if ((buttons & ((1<<NES_LEFT) | (1<<NES_SELECT)))==0) {
+		k |= GENERIC_OTHER;
+	}
+	else {
+		if ((buttons & (1<<NES_UP))==0)	 	k |= GENERIC_UP;
+		if ((buttons & (1<<NES_DOWN))==0)		k |= GENERIC_DOWN;
+		if ((buttons & (1<<NES_LEFT))==0)		k |= GENERIC_LEFT;
+		if ((buttons & (1<<NES_RIGHT))==0) 	k |= GENERIC_RIGHT;
+		if ((buttons & (1<<NES_A))==0) 		k |= GENERIC_FIRE | GENERIC_FIRE_A;
+		if ((buttons & (1<<NES_B))==0) 		k |= GENERIC_FIRE_B;
+		if ((buttons & (1<<NES_START))==0) 	k |= GENERIC_START;
+		if ((buttons & (1<<NES_SELECT))==0)	k |= GENERIC_SELECT;
+	}
+#endif
+#ifdef SNES_CONTROLLER
+	/* on SNES, menu is LEFT + RIGHT buttons */
+	if ((buttons & ((1<<SNES_L) | (1<<SNES_R)))==0) {
+		k |= GENERIC_OTHER;
+	}
+	else {
+		if ((buttons & (1 << SNES_UP)) == 0)		k |= GENERIC_UP;
+		if ((buttons & (1 << SNES_DOWN)) == 0) 		k |= GENERIC_DOWN;
+		if ((buttons & (1 << SNES_LEFT)) == 0)		k |= GENERIC_LEFT;
+		if ((buttons & (1 << SNES_RIGHT)) == 0) 	k |= GENERIC_RIGHT;
+		if ((buttons & (1 << SNES_A)) == 0) 		k |= GENERIC_FIRE | GENERIC_FIRE_A;
+		if ((buttons & (1 << SNES_B)) == 0) 		k |= GENERIC_FIRE_B;
+		if ((buttons & (1 << SNES_START)) == 0) 	k |= GENERIC_START;
+		if ((buttons & (1 << SNES_SELECT)) == 0)	k |= GENERIC_SELECT;
+	}
+#endif
+
+	/* Quack hard wired player A or B */
+	if (gpio_get_level((gpio_num_t)GPIO_ADBSRC) == 0) {
+		nc_set(0,k); // no repeat period
+		nc_set(1,0); // no repeat period
+	}
+	else {
+		nc_set(0,0); // no repeat period
+		nc_set(1,k); // no repeat period
+	}
+
+	//printf("NC:\t%04X, %X\n", buttons, k);
+	if (k && buttons)
+		gpio_set_level((gpio_num_t)GPIO_YELLOWLED, 1);
+	else
+		gpio_set_level((gpio_num_t)GPIO_YELLOWLED, 0);
+
+	return get_hid_nc(dst);
+}
+#endif
