[{"element_type":"text","content":"\n        \u003cp\u003eHere's some Arduino code for the Teensy 4.1 (Adafruit \u003cspan\u003eProduct ID:\u0026nbsp;\u003c/span\u003e\u003cspan\u003e4622)\u003c/span\u003e that lets my Spacemouse Wireless\u0026nbsp; from 3Dconnexion appear as a standard USB midi controller to my computer, and presumably any other USB midi hosting device. It provides six high-resolution CC pairs from the knob motion, and two on/off CC's for the two buttons.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eThe Teensy needs a jumper between the board Vin and the USB Host 5v pins to power the Spacemouse Dongle (unless a powered USB hub is inserted between them).\u0026nbsp; \u0026nbsp;\u003c/p\u003e\n\u003cp\u003eThe teensy, of course, also needs to be fitted with\u0026nbsp; a USB Host socket connected to the USB host pins on the board to to recieve the 3DConnexion dongle or USB wire.\u0026nbsp;\u003c/p\u003e\n\u003cp\u003eThe code was provided by ChatGPT after some relentless back and forth.\u0026nbsp; I'm pretty clueless myself about it. But for a souless device planning to take over the world and annihilate us all, The Chatster has so far proved to be very patient and accomodating. Beyond the obviously tunable lines,\u0026nbsp; the LLM should be able to adapt it more to your liking, even if your skills are as meager as my own.\u003c/p\u003e\n\u003cp\u003e\u0026nbsp; \u0026nbsp;\u0026nbsp;\u003c/p\u003e\n      ","metadata":{}},{"element_type":"code","content":"/* Teensy 4.1 — SpaceMouse (USB Host via JoystickController) → USB-MIDI (Serial+MIDI)\n * Fixed full-scale, change-gated 14-bit CC. Buttons → two CCs (127/0).\n * NEW: sendAxisPair() sends LSB whenever MSB changes (prevents stale LSB pairing in Pd).\n * Optional SNAP_TO_CENTER to force perfect 8192 (MSB=64, LSB=0) near rest.\n */\n\n#include \u003cUSBHost_t36.h\u003e\n\n// -------- Host stack --------\nUSBHost            usb;\nUSBHub             hub1(usb);             // optional hub support\nUSBHIDParser       hid1(usb), hid2(usb);\nJoystickController joy1(usb);             // first joystick / multi-axis device\n\n// -------- User config --------\nconst uint8_t MIDI_CH = 1;                // MIDI channel 1..16\n\n// CC map (MSB/LSB) for X,Y,Z,Rx,Ry,Rz\nconst uint8_t CC_X_MSB=16, CC_X_LSB=48;\nconst uint8_t CC_Y_MSB=17, CC_Y_LSB=49;\nconst uint8_t CC_Z_MSB=18, CC_Z_LSB=50;\nconst uint8_t CC_RX_MSB=19, CC_RX_LSB=51;\nconst uint8_t CC_RY_MSB=20, CC_RY_LSB=52;\nconst uint8_t CC_RZ_MSB=21, CC_RZ_LSB=53;\n\n// Buttons (on/off 127/0)\nconst uint8_t CC_BTN_L = 22;\nconst uint8_t CC_BTN_R = 23;\n\n// Axis inversion (+1 or -1) — flip to -1 for any axis that feels reversed\nint AX_INV[6] = { +1, +1, +1, +1, +1, +1 };\n\n// Fixed full-scale per axis (tune if your device tops out differently)\nconst int FS_X  = 200;\nconst int FS_Y  = 200;\nconst int FS_Z  = 200;\nconst int FS_RX = 200;\nconst int FS_RY = 200;\nconst int FS_RZ = 200;\n\n// Poll/send throttle (ms)\nconst uint32_t SEND_PERIOD_MS = 8;\n\n// Optional: snap to exact 8192 when very close to center\nconst bool  SNAP_TO_CENTER    = true;\nconst float SNAP_THRESHOLD    = 0.004f;   // ~0.4% of full scale (~±65 in 14-bit)\n\n// -------- State --------\nuint8_t lastMSB[6] = {255,255,255,255,255,255};\nuint8_t lastLSB[6] = {255,255,255,255,255,255};\nbool    lastBtnL=false, lastBtnR=false;\nuint32_t lastPollMs = 0;\n\n// -------- Helpers --------\nstatic inline float normAxisFixed(int v, int fullScale){\n  if (v \u003c -fullScale) v = -fullScale;\n  if (v \u003e  fullScale) v =  fullScale;\n  return (float)v / (float)fullScale; // -1..+1\n}\n\nstatic inline void to14(float norm, uint8_t \u0026msb, uint8_t \u0026lsb){\n  if (norm \u003c -1.f) norm = -1.f; else if (norm \u003e 1.f) norm = 1.f;\n  int v14;\n  if (SNAP_TO_CENTER \u0026\u0026 fabsf(norm) \u003c SNAP_THRESHOLD) {\n    v14 = 8192;                       // force perfect center\n  } else {\n    v14 = (int)lroundf((norm * 0.5f + 0.5f) * 16383.0f);\n  }\n  if (v14 \u003c 0) v14 = 0; else if (v14 \u003e 16383) v14 = 16383;\n  msb = (v14 \u003e\u003e 7) \u0026 0x7F;\n  lsb = v14 \u0026 0x7F;\n}\n\n// NEW: send LSB whenever MSB changes so Pd always pairs a fresh LSB with the new MSB.\nstatic inline void sendAxisPair(uint8_t ccMSB, uint8_t ccLSB,\n                                uint8_t \u0026last_msb, uint8_t \u0026last_lsb,\n                                float norm){\n  uint8_t msb, lsb; to14(norm, msb, lsb);\n\n  if (msb != last_msb) {\n    usbMIDI.sendControlChange(ccMSB, msb, MIDI_CH);\n    usbMIDI.sendControlChange(ccLSB, lsb, MIDI_CH);  // force-send LSB with new MSB\n    last_msb = msb;\n    last_lsb = lsb;\n    return;\n  }\n  if (lsb != last_lsb) {\n    usbMIDI.sendControlChange(ccLSB, lsb, MIDI_CH);\n    last_lsb = lsb;\n  }\n}\n\nstatic inline void sendBtn(uint8_t cc, bool down){\n  usbMIDI.sendControlChange(cc, down ? 127 : 0, MIDI_CH);\n}\n\n// -------- Setup / Loop --------\nvoid setup(){\n  Serial.begin(115200);\n  usb.begin();\n  pinMode(LED_BUILTIN, OUTPUT);\n  Serial.println(\"SpaceMouse → USB-MIDI (paired LSB/MSB) ready.\");\n}\n\nvoid loop(){\n  usb.Task();\n  while (usbMIDI.read()) {} // drain incoming MIDI if any\n\n  uint32_t now = millis();\n  if (now - lastPollMs \u003c SEND_PERIOD_MS) return;\n  lastPollMs = now;\n\n  if (!joy1){\n    static uint32_t t0=0; \n    if (now - t0 \u003e 1000){ t0 = now; Serial.println(\"Waiting for joystick/multi-axis...\"); }\n    return;\n  }\n\n  // Read raw axes (0..5). Swap these if your unit orders them differently.\n  int axX  = joy1.getAxis(0);\n  int axY  = joy1.getAxis(1);\n  int axZ  = joy1.getAxis(2);\n  int axRx = joy1.getAxis(3);\n  int axRy = joy1.getAxis(4);\n  int axRz = joy1.getAxis(5);\n\n  // Normalize with fixed scales \u0026 optional inversion\n  float X  = AX_INV[0] * normAxisFixed(axX,  FS_X);\n  float Y  = AX_INV[1] * normAxisFixed(axY,  FS_Y);\n  float Z  = AX_INV[2] * normAxisFixed(axZ,  FS_Z);\n  float RX = AX_INV[3] * normAxisFixed(axRx, FS_RX);\n  float RY = AX_INV[4] * normAxisFixed(axRy, FS_RY);\n  float RZ = AX_INV[5] * normAxisFixed(axRz, FS_RZ);\n\n  // Send CC pairs (paired LSB/MSB behavior)\n  sendAxisPair(CC_X_MSB,  CC_X_LSB,  lastMSB[0], lastLSB[0], X);\n  sendAxisPair(CC_Y_MSB,  CC_Y_LSB,  lastMSB[1], lastLSB[1], Y);\n  sendAxisPair(CC_Z_MSB,  CC_Z_LSB,  lastMSB[2], lastLSB[2], Z);\n  sendAxisPair(CC_RX_MSB, CC_RX_LSB, lastMSB[3], lastLSB[3], RX);\n  sendAxisPair(CC_RY_MSB, CC_RY_LSB, lastMSB[4], lastLSB[4], RY);\n  sendAxisPair(CC_RZ_MSB, CC_RZ_LSB, lastMSB[5], lastLSB[5], RZ);\n\n  // Buttons (bit0, bit1 typical)\n  uint32_t btns = joy1.getButtons();\n  bool L = (btns \u0026 0x01);\n  bool R = (btns \u0026 0x02);\n  if (L != lastBtnL){ sendBtn(CC_BTN_L, L); lastBtnL = L; }\n  if (R != lastBtnR){ sendBtn(CC_BTN_R, R); lastBtnR = R; }\n\n  // Serial debug for X (prints whenever X's MSB/LSB changed)\n  {\n    uint8_t msbX = lastMSB[0], lsbX = lastLSB[0];\n    static uint8_t prevM=255, prevL=255;\n    if (msbX != prevM || lsbX != prevL) {\n      int val14 = ((int)msbX \u003c\u003c 7) | lsbX;\n      Serial.print(\"X norm=\");\n      Serial.print(X, 3);\n      Serial.print(\"  MSB/LSB=\");\n      Serial.print(msbX);\n      Serial.print(\"/\");\n      Serial.print(lsbX);\n      Serial.print(\"  val14=\");\n      Serial.println(val14);\n      prevM = msbX; prevL = lsbX;\n    }\n  }\n\n  // Heartbeat (device present)\n  static uint32_t t1=0; \n  if (now - t1 \u003e 500){ t1 = now; digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); }\n}","metadata":{"language":"auto","linenums":false,"filename":""}}]