From c58868099af9b4f7a119f81d8064250441a9621b Mon Sep 17 00:00:00 2001 From: Tom Armitage Date: Thu, 9 Jan 2025 12:42:57 +0000 Subject: [PATCH 1/4] Kick off new release number. --- 16next.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/16next.h b/16next.h index d92a88a..7b640a1 100644 --- a/16next.h +++ b/16next.h @@ -6,8 +6,8 @@ #include "pico/i2c_slave.h" #define FIRMWARE_VERSION_MAJOR 3 -#define FIRMWARE_VERSION_MINOR 0 -#define FIRMWARE_VERSION_POINT 3 +#define FIRMWARE_VERSION_MINOR 1 +#define FIRMWARE_VERSION_POINT 0 #define FADER_COUNT 16 From 6ad1983155c879b6c5269fd185a684f679530ac8 Mon Sep 17 00:00:00 2001 From: Tom Armitage Date: Thu, 9 Jan 2025 16:19:12 +0000 Subject: [PATCH 2/4] Support for high-resolution CCs. This comes in a few places: - firstly, adding six bytes to the memory map to store the 32 bits of data describing which faders are high-resolution. - secondly, reading/writing high-resolution data to both USB and TRS as appropriate - thirdly, handling the new data in save/load. It's not particularly attractive, but I believe it works just fine. --- 16next.cpp | 60 +++++++++++++++++++++++++++++++++++++++----------- README.md | 8 ++++++- SYSEX_SPEC.md | 7 +++--- lib/config.cpp | 26 ++++++++++++++++++---- lib/config.h | 2 ++ lib/sysex.cpp | 8 +++---- 6 files changed, 85 insertions(+), 26 deletions(-) diff --git a/16next.cpp b/16next.cpp index b79398f..84b8b5f 100644 --- a/16next.cpp +++ b/16next.cpp @@ -42,7 +42,7 @@ uint8_t sysexOffset = 0; // where in the buffer we start writing to. // fader 4 is on mux input 1 const int faderLookup[] = {7, 6, 5, 4, 3, 2, 1, 0, 8, 9, 10, 11, 12, 13, 14, 15}; -int previousValues[16]; +uint16_t previousValues[16]; int i2cData[16]; int muxMask; @@ -288,25 +288,59 @@ void updateControls(bool force) { } // test the scaled version against the previous CC. - uint8_t outputValue = analog[i]->getValue() >> 5; - if ((outputValue != previousValues[i]) || force) { - previousValues[i] = outputValue; - uint8_t controllerIndex = i; // TODO is this right? + uint16_t usbOutputValue; + uint16_t trsOutputValue; + bool usbHighResolution = controller.usbHighResolution[i]; // TODO: also do for TRS + bool trsHighResolution = controller.trsHighResolution[i]; // TODO: also do for TRS + + uint8_t usbOutputBits = usbHighResolution ? 14 : 7; + uint8_t trsOutputBits = trsHighResolution ? 14 : 7; + + usbOutputValue = usbHighResolution ? analog[i]->getValue() << 2 : analog[i]->getValue() >> 5; + trsOutputValue = trsHighResolution ? analog[i]->getValue() << 2 : analog[i]->getValue() >> 5; + + if ((usbOutputValue != previousValues[i]) || force) { + previousValues[i] = usbOutputValue; // yes, I know USB is driving things. + uint8_t controllerIndex = i; if (controller.rotated) { controllerIndex = FADER_COUNT - 1 - i; - outputValue = 127 - outputValue; + usbOutputValue = ((1 << usbOutputBits) - 1) - usbOutputValue; + trsOutputValue = ((1 << trsOutputBits) - 1) - trsOutputValue; } // Send CC on appropriate USB channel - uint8_t cc[3] = {(uint8_t)(0xB0 | controller.usbMidiChannels[controllerIndex] - 1), controller.usbCCs[controllerIndex], - outputValue}; uint8_t cable_num = 0; - tud_midi_stream_write(cable_num, cc, 3); + if (usbHighResolution) { + uint8_t msb = (usbOutputValue >> 7) & 0x7F; + uint8_t lsb = usbOutputValue & 0x7F; + + uint8_t msbCCData[3] = {(uint8_t)(0xB0 | controller.usbMidiChannels[controllerIndex] - 1), controller.usbCCs[controllerIndex], msb}; + uint8_t lsbCCData[3] = {(uint8_t)(0xB0 | controller.usbMidiChannels[controllerIndex] - 1), controller.usbCCs[controllerIndex] + 32, lsb}; + + tud_midi_stream_write(cable_num, msbCCData, 3); + tud_midi_stream_write(cable_num, lsbCCData, 3); + } else { + uint8_t ccData[3] = {(uint8_t)(0xB0 | controller.usbMidiChannels[controllerIndex] - 1), controller.usbCCs[controllerIndex], + usbOutputValue}; + tud_midi_stream_write(cable_num, ccData, 3); + } - // Send CC on appropriate TRS channel - uint8_t trs_cc[3] = {(uint8_t)(0xB0 | controller.trsMidiChannels[controllerIndex] - 1), controller.trsCCs[controllerIndex], outputValue}; - // tud_midi_stream_write(cable_num, cc, 3); - midi_uart_write_tx_buffer(midi_uart_instance, trs_cc, 3); + // Send CC on appropiate TRS channel + // TODO: if TRS high resolution + if (trsHighResolution) { + uint8_t msb = (trsOutputValue >> 7) & 0x7F; + uint8_t lsb = trsOutputValue & 0x7F; + + uint8_t trs_msbCCData[3] = {(uint8_t)(0xB0 | controller.trsMidiChannels[controllerIndex] - 1), controller.trsCCs[controllerIndex], msb}; + uint8_t trs_lsbCCData[3] = {(uint8_t)(0xB0 | controller.trsMidiChannels[controllerIndex] - 1), controller.trsCCs[controllerIndex] + 32, lsb}; + // tud_midi_stream_write(cable_num, cc, 3); + midi_uart_write_tx_buffer(midi_uart_instance, trs_msbCCData, 3); + midi_uart_write_tx_buffer(midi_uart_instance, trs_lsbCCData, 3); + } else { + uint8_t ccData[3] = {(uint8_t)(0xB0 | controller.usbMidiChannels[controllerIndex] - 1), controller.usbCCs[controllerIndex], + trsOutputValue}; + midi_uart_write_tx_buffer(midi_uart_instance, ccData, 3); + } midiActivity = true; midiActivityLightOffAt = make_timeout_time_us(MIDI_BLINK_DURATION); diff --git a/README.md b/README.md index 7b69d22..99cd373 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ It is edited and managed via a [web-based editor][editor]. ## Firmware Version -3.0.3 +3.1.0 ## Introduction @@ -127,6 +127,12 @@ The user data stored in flash (and indeed, sent via sysex) has the following sha | 32-47 | 0-15 | Channel for each control (TRS) | | 48-63 | 0-127 | CC for each control (USB) | | 64-79 | 0-127 | CC for each control (TRS) | +| 80-82 | 0-127 | Booleans for high-res mode (USB)\* | +| 83-85 | 0-127 | Booleans for high-res mode (TRS)\* | + +### High-res mode booleans + +Each controller can optionally operate in high res mode, sending 14-bit data as two CCs: an "MSB" (albeit 7-bits) on (CC), and an "LSB" (again, 7-bits) on (CC+32). We store this option as a boolean. Because Sysex data can only transmit 7-bits (0x00-0x7F), we need to store this inside three bytes of data. To keep things straightforward, we'll store the high-res mode for USB and TRS as two separate values, each requiring 3 7-bit values to describe. ## Debug connector diff --git a/SYSEX_SPEC.md b/SYSEX_SPEC.md index b334673..70b11e2 100644 --- a/SYSEX_SPEC.md +++ b/SYSEX_SPEC.md @@ -8,11 +8,11 @@ Request for 16n to transmit current state via sysex. No other payload. ## `0x0F` - "c0nFig" -"Here is my current config." Only sent by 16n as an outbound message, in response to `0x1F`. Payload of 80 bytes, describing current EEPROM state. +"Here is my current config." Only sent by 16n as an outbound message, in response to `0x1F`. Payload of 86 bytes, describing current EEPROM state. ## `0x0E` - "c0nfig Edit" -"Here is a new complete configuration for you". Payload (other than mfg header, top/tail, etc) of 80 bytes to go straight into EEPROM, according to the memory map described in `README.md`. +"Here is a new complete configuration for you". Payload (other than mfg header, top/tail, etc) of 86 bytes to go straight into EEPROM, according to the memory map described in `README.md`. ## `0x0D` - "c0nfig edit (Device options)" @@ -20,7 +20,6 @@ Request for 16n to transmit current state via sysex. No other payload. ## `0x0C` - "c0nfig edit (usb options)" - "Here is a new set of USB options for you". Payload (other than mfg header, top/tail, etc) of 32 bytes to go straight into appropriate locations of EEPROM, according to the memory map described in `README.md`. ## `0x0B` - "c0nfig edit (trs options)" @@ -29,4 +28,4 @@ Request for 16n to transmit current state via sysex. No other payload. ## `0x1A` - "1nitiAlize memory" -"Wipe the EEPROM and force factory settings". +"Wipe the EEPROM and force factory settings". diff --git a/lib/config.cpp b/lib/config.cpp index 0f6d485..2f1e3ac 100644 --- a/lib/config.cpp +++ b/lib/config.cpp @@ -1,7 +1,7 @@ #include "config.h" #include "flash_onboard.h" -const uint8_t memoryMapLength = 80; +const uint8_t memoryMapLength = 86; // default memorymap // | Address | Format | Description | @@ -18,20 +18,24 @@ const uint8_t memoryMapLength = 80; // | 32-47 | 1-16 | Channel for each control (TRS) | // | 48-63 | 0-127 | CC for each control (USB) | // | 64-79 | 0-127 | CC for each control (TRS) | +// | 80-82 | 0-127 | Booleans for high-res mode (USB) | +// | 83-85 | 0-127 | Booleans for high-res mode (TRS) | uint8_t defaultMemoryMap[] = { 0, 1, 0, 0, 0, 0, 0, 0, // 0-7 0, 0, 0, 0, 0, 0, 0, 0, // 8-15 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 16-31 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32-47 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 48-63 - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47 // 64-79 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, // 64-79 + 0, 0, 0, // 80-82 + 0, 0, 0 // 83-85 }; void updateConfig(uint8_t *incomingSysex, uint8_t incomingSysexLength, ControllerConfig *cConfig) { // OK: uint8_t newMemoryMap[memoryMapLength]; - // 1) read the data that's just come in, and extract the 80 bytes of memory + // 1) read the data that's just come in, and extract the 86 bytes of memory // to a variable we offset by five to strip: SYSEX_START,MFG0,MFG1,MFG2,MSG // and then also to strip // * device ID @@ -48,7 +52,7 @@ void updateConfig(uint8_t *incomingSysex, uint8_t incomingSysexLength, Controlle } void loadConfig(ControllerConfig *cConfig, bool setDefault) { - // read 80 bytes from internal flash + // read 86 bytes from internal flash uint8_t buf[memoryMapLength]; readFlash(buf, memoryMapLength); // if the 2nd byte is unwritten, that means we should write the default @@ -83,6 +87,20 @@ void applyConfig(uint8_t *conf, ControllerConfig *cConfig) { for (uint8_t i = 0; i < 16; i++) { cConfig->trsCCs[i] = conf[64 + i]; } + + // extract and configure high-resolution data + uint16_t usbHighResValue = (conf[80] & 0x7F) | + ((conf[81] & 0x7F) << 7) | + ((conf[82] & 0x03) << 14); + + uint16_t trsHighResValue = (conf[83] & 0x7F) | + ((conf[84] & 0x7F) << 7) | + ((conf[85] & 0x03) << 14); + + for (uint8_t i = 0; i < 16; i++) { + cConfig->usbHighResolution[i] = (usbHighResValue & (1 << i)) != 0; + cConfig->trsHighResolution[i] = (trsHighResValue & (1 << i)) != 0; + } } void saveConfig(uint8_t *config) { diff --git a/lib/config.h b/lib/config.h index 3384a15..98a52e6 100644 --- a/lib/config.h +++ b/lib/config.h @@ -18,6 +18,8 @@ struct ControllerConfig { uint8_t usbCCs[16]; uint8_t trsMidiChannels[16]; uint8_t trsCCs[16]; + bool usbHighResolution[16]; + bool trsHighResolution[16]; }; extern const uint8_t memoryMapLength; diff --git a/lib/sysex.cpp b/lib/sysex.cpp index 076f50e..0cd15dc 100644 --- a/lib/sysex.cpp +++ b/lib/sysex.cpp @@ -23,9 +23,9 @@ void sendCurrentConfig() { uint8_t configDataLength = 4 + memoryMapLength; uint8_t currentConfigData[configDataLength]; - // read 80 bytes from internal flash - uint8_t buf[80]; - readFlash(buf, 80); + // read 86 bytes from internal flash + uint8_t buf[86]; + readFlash(buf, 86); // build a message from the version number... currentConfigData[0] = DEVICE_INDEX; @@ -33,7 +33,7 @@ void sendCurrentConfig() { currentConfigData[2] = FIRMWARE_VERSION_MINOR; currentConfigData[3] = FIRMWARE_VERSION_POINT; - // ... and the first 80 bytes of the external data + // ... and the first 86 bytes of the external data for (uint8_t i = 0; i < memoryMapLength; i++) { currentConfigData[i + 4] = buf[i]; } From eee8535e449446735d09f8dd72e3fe97e12c07b0 Mon Sep 17 00:00:00 2001 From: Tom Armitage Date: Thu, 9 Jan 2025 16:28:22 +0000 Subject: [PATCH 3/4] Extract memory map length to a macro/define. --- 16next.h | 2 ++ lib/config.cpp | 15 +++++++-------- lib/config.h | 1 - lib/sysex.cpp | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/16next.h b/16next.h index 7b640a1..d233450 100644 --- a/16next.h +++ b/16next.h @@ -11,6 +11,8 @@ #define FADER_COUNT 16 +#define MEMORY_MAP_LENGTH 86 + // define the board type here: // SIXTEEN_RX = 16rx // SIXTEEN_NX = 16nx diff --git a/lib/config.cpp b/lib/config.cpp index 2f1e3ac..ad3ce32 100644 --- a/lib/config.cpp +++ b/lib/config.cpp @@ -1,8 +1,7 @@ #include "config.h" +#include "16next.h" #include "flash_onboard.h" -const uint8_t memoryMapLength = 86; - // default memorymap // | Address | Format | Description | // |---------|--------|------------------------------------| @@ -20,7 +19,7 @@ const uint8_t memoryMapLength = 86; // | 64-79 | 0-127 | CC for each control (TRS) | // | 80-82 | 0-127 | Booleans for high-res mode (USB) | // | 83-85 | 0-127 | Booleans for high-res mode (TRS) | -uint8_t defaultMemoryMap[] = { +uint8_t defaultMemoryMap[] = { 0, 1, 0, 0, 0, 0, 0, 0, // 0-7 0, 0, 0, 0, 0, 0, 0, 0, // 8-15 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 16-31 @@ -33,14 +32,14 @@ uint8_t defaultMemoryMap[] = { void updateConfig(uint8_t *incomingSysex, uint8_t incomingSysexLength, ControllerConfig *cConfig) { // OK: - uint8_t newMemoryMap[memoryMapLength]; + uint8_t newMemoryMap[MEMORY_MAP_LENGTH]; // 1) read the data that's just come in, and extract the 86 bytes of memory // to a variable we offset by five to strip: SYSEX_START,MFG0,MFG1,MFG2,MSG // and then also to strip // * device ID // * firmware MAJ/Min/POINT - for (uint8_t i = 0; i < memoryMapLength; i++) { + for (uint8_t i = 0; i < MEMORY_MAP_LENGTH; i++) { newMemoryMap[i] = incomingSysex[i + 9]; } @@ -53,8 +52,8 @@ void updateConfig(uint8_t *incomingSysex, uint8_t incomingSysexLength, Controlle void loadConfig(ControllerConfig *cConfig, bool setDefault) { // read 86 bytes from internal flash - uint8_t buf[memoryMapLength]; - readFlash(buf, memoryMapLength); + uint8_t buf[MEMORY_MAP_LENGTH]; + readFlash(buf, MEMORY_MAP_LENGTH); // if the 2nd byte is unwritten, that means we should write the default // settings to flash if (setDefault && (buf[1] == 0xFF)) { @@ -104,7 +103,7 @@ void applyConfig(uint8_t *conf, ControllerConfig *cConfig) { } void saveConfig(uint8_t *config) { - writeFlash(config, memoryMapLength); + writeFlash(config, MEMORY_MAP_LENGTH); } void setDefaultConfig() { diff --git a/lib/config.h b/lib/config.h index 98a52e6..f1d0f64 100644 --- a/lib/config.h +++ b/lib/config.h @@ -22,7 +22,6 @@ struct ControllerConfig { bool trsHighResolution[16]; }; -extern const uint8_t memoryMapLength; extern uint8_t defaultMemoryMap[]; void updateConfig(uint8_t *incomingSysex, uint8_t incomingSysexLength, ControllerConfig *cConfig); diff --git a/lib/sysex.cpp b/lib/sysex.cpp index 0cd15dc..d8048e8 100644 --- a/lib/sysex.cpp +++ b/lib/sysex.cpp @@ -20,12 +20,12 @@ bool copySysexStreamToBuffer(uint8_t *syxBuffer, uint8_t *inputBuffer, uint8_t s void sendCurrentConfig() { // current Data length = memory + 3 bytes for firmware version + 1 byte for device ID - uint8_t configDataLength = 4 + memoryMapLength; + uint8_t configDataLength = 4 + MEMORY_MAP_LENGTH; uint8_t currentConfigData[configDataLength]; // read 86 bytes from internal flash - uint8_t buf[86]; - readFlash(buf, 86); + uint8_t buf[MEMORY_MAP_LENGTH]; + readFlash(buf, MEMORY_MAP_LENGTH); // build a message from the version number... currentConfigData[0] = DEVICE_INDEX; @@ -34,7 +34,7 @@ void sendCurrentConfig() { currentConfigData[3] = FIRMWARE_VERSION_POINT; // ... and the first 86 bytes of the external data - for (uint8_t i = 0; i < memoryMapLength; i++) { + for (uint8_t i = 0; i < MEMORY_MAP_LENGTH; i++) { currentConfigData[i + 4] = buf[i]; } From 5dcffaea0bd672fff0e5971d165eeb3da4982c67 Mon Sep 17 00:00:00 2001 From: Tom Armitage Date: Sat, 11 Jan 2025 16:00:40 +0000 Subject: [PATCH 4/4] High resolution mode now works rotated. BasicalLy: - the index of the loop is stepping through physical faders... - ...but we care about logical controllers, which alter when rotated - working out if a fader is high-res or not should respect that. --- 16next.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/16next.cpp b/16next.cpp index 84b8b5f..e502f46 100644 --- a/16next.cpp +++ b/16next.cpp @@ -271,6 +271,12 @@ void updateControls(bool force) { #endif analog[i]->update(rawAdcValue); + uint8_t controllerIndex = i; + + if (controller.rotated) { + controllerIndex = FADER_COUNT - 1 - i; + } + if (analog[i]->hasChanged() || force) { if (force) { // if we're being asked to update all our values, we _really_ would like a read, please. @@ -290,8 +296,8 @@ void updateControls(bool force) { // test the scaled version against the previous CC. uint16_t usbOutputValue; uint16_t trsOutputValue; - bool usbHighResolution = controller.usbHighResolution[i]; // TODO: also do for TRS - bool trsHighResolution = controller.trsHighResolution[i]; // TODO: also do for TRS + bool usbHighResolution = controller.rotated ? controller.usbHighResolution[controllerIndex] : controller.usbHighResolution[controllerIndex]; + bool trsHighResolution = controller.rotated ? controller.trsHighResolution[controllerIndex] : controller.trsHighResolution[controllerIndex]; uint8_t usbOutputBits = usbHighResolution ? 14 : 7; uint8_t trsOutputBits = trsHighResolution ? 14 : 7; @@ -300,12 +306,11 @@ void updateControls(bool force) { trsOutputValue = trsHighResolution ? analog[i]->getValue() << 2 : analog[i]->getValue() >> 5; if ((usbOutputValue != previousValues[i]) || force) { - previousValues[i] = usbOutputValue; // yes, I know USB is driving things. - uint8_t controllerIndex = i; + previousValues[i] = usbOutputValue; // yes, I know USB is driving things. + if (controller.rotated) { - controllerIndex = FADER_COUNT - 1 - i; - usbOutputValue = ((1 << usbOutputBits) - 1) - usbOutputValue; - trsOutputValue = ((1 << trsOutputBits) - 1) - trsOutputValue; + usbOutputValue = ((1 << usbOutputBits) - 1) - usbOutputValue; + trsOutputValue = ((1 << trsOutputBits) - 1) - trsOutputValue; } // Send CC on appropriate USB channel