diff --git a/MockBand.ino b/MockBand.ino index c88a920..3b3f96c 100644 --- a/MockBand.ino +++ b/MockBand.ino @@ -8,12 +8,20 @@ //#include "blue.hh" // Ginnie's blue guitar #include "standard.hh" // Standard pins -// How often do you want to send an update? -// Smaller number: lower latency, but more bounces -// Bigger number: higher latency -// 20ms feels pretty good to me +// Maximum time between wammy bar updates. #define UPDATE_INTERVAL_MS 20 +// After an edge on a pin, stop listening for this long, to debounce it +#define SILENCE_INTERVAL_MS 40 + +// Your measured samples per frame, more or less +#define SAMPLES_PER_FRAME 127 + +// Some arithmetic for the compiler, to make the code fast +#define SAMPLES_PER_MS (SAMPLES_PER_FRAME / UPDATE_INTERVAL_MS) +#define SILENCE_SAMPLES (SAMPLES_PER_MS * SILENCE_INTERVAL_MS) + + InstrumentButtonState buttonState; void setup() { @@ -48,40 +56,57 @@ void setup() { buttonState.finalConstant = 0x0200020002000200; } +// Order of pins in sample +uint8_t pins[] = { + BUTTON_BLUE, + BUTTON_GREEN, + BUTTON_RED, + BUTTON_YELLOW, + BUTTON_ORANGE, + TILT_SWITCH, + STRUM_UP, // Not in USB packet + STRUM_DOWN, // Not in USB packet + BUTTON_MINUS, + BUTTON_PLUS, + SOLO_BLUE, // Not in USB packet + SOLO_GREEN, // Not in USB packet + SOLO_RED, // Not in USB packet + SOLO_YELLOW, // Not in USB packet + SOLO_ORANGE, // Not in USB packet +}; +#define npins (sizeof(pins) / sizeof(*pins)) + // The 3.3v Pro Micro is on the slow side. // Our strategy is to poll button state as quickly as possible, -// send an update every 10ms, // and hope we don't miss anything while we're doing USB stuff. void loop() { - register uint16_t buttons = 0; + uint16_t buttons = 0; + uint16_t samples = 0; unsigned long next = 0; + uint16_t silence[npins] = {0}; while (1) { unsigned long now = millis(); + uint16_t edge = 0; - buttons |= ~(0 - | (digitalRead(BUTTON_BLUE) << 0) - | (digitalRead(BUTTON_GREEN) << 1) - | (digitalRead(BUTTON_RED) << 2) - | (digitalRead(BUTTON_YELLOW) << 3) - | (digitalRead(BUTTON_ORANGE) << 4) - | (digitalRead(TILT_SWITCH) << 5) - | (digitalRead(STRUM_UP) << 6) // Not in USB packet - | (digitalRead(STRUM_DOWN) << 7) // Not in USB packet - | (digitalRead(BUTTON_MINUS) << 8) - | (digitalRead(BUTTON_PLUS) << 9) - | (digitalRead(SOLO_BLUE) << 10) // Not in USB packet - | (digitalRead(SOLO_GREEN) << 11) // Not in USB packet - | (digitalRead(SOLO_RED) << 12) // Not in USB packet - | (digitalRead(SOLO_YELLOW) << 13) // Not in USB packet - | (digitalRead(SOLO_ORANGE) << 14) // Not in USB packet - ); + samples++; - if (now < next) { + for (uint8_t i = 0; i < npins; i++) { + if (silence[i]) { + silence[i]--; + } else if (bitRead(buttons, i) != !digitalRead(pins[i])) { + edge |= bit(i); + silence[i] = SILENCE_SAMPLES; + } + } + buttons ^= edge; + + // We've sampled everything. Is it time to do calculations and USB? + if (!edge && (next > now)) { continue; } - next = now + 20; - + next = now + UPDATE_INTERVAL_MS; + buttonState.buttons = (buttons & 0b1100111111); // All directly-mappable bits buttonState.buttons |= ((buttons >> 10) & 0b11111); // Solo keys bitWrite(buttonState.buttons, 6, (buttons >> 10) & 0b11111); // Solo bit @@ -96,9 +121,12 @@ void loop() { buttonState.axis[2] = analogRead(ANALOG_WAMMY) / 4; + // The second Y axis doesn't appear to be used, so I'm logging sample rate with it + buttonState.axis[3] = samples & 0xff; // most things map this to [-1,1] + // Send an update HID().SendReport(0, (uint8_t *)&buttonState, 27); - buttons = 0; + samples = 0; } } diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..87eb352 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,39 @@ +I did some performance testing. + +The Pro Micro runs an Atmel 32u4 at 8MHz. +If the code does nothing else, +I was able to poll all pins around 13 times every 1ms, +or 13kHz. + + +Sample Rate +----------- + +The number of samples taken since the last HID report +is sent as the second Y axis. +If you use the +[included gamepad tester](gamepad.html), +it will show the approximate number of samples as an integer, +for Y axis 2. + +This is approximate, +because the browser encodes the value as a real number between -1 and 1. +We convert it back, but may lose a little precision. +It's close enough for me, +hopefully it's close enough for you. + + +Debouncing +---------- + +Using `millis()` time to debounce the switch +roughly halved my sample frequency. +So instead, I do some preprocessor arithmetic +to calculate how many samples to take after an edge, +in order to debounce switches. + +The drum controller was a partcular pain: +in addition to the switch bouncing, +the stick was bouncing on the rubber pad. +I settled on a 40ms silence window as feeling pretty good. +You can adjust this if you want to. diff --git a/gamepad.html b/gamepad.html new file mode 100644 index 0000000..0d75c69 --- /dev/null +++ b/gamepad.html @@ -0,0 +1,264 @@ + + + + +

HTML5 Gamepad Test

+
running:
+
+ +