Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support high-resolution (14-bit) MIDI CCs. #15

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 53 additions & 14 deletions 16next.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand All @@ -288,25 +294,58 @@ 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.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;

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.

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);
Expand Down
6 changes: 4 additions & 2 deletions 16next.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
#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

#define MEMORY_MAP_LENGTH 86

// define the board type here:
// SIXTEEN_RX = 16rx
// SIXTEEN_NX = 16nx
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ It is edited and managed via a [web-based editor][editor].

## Firmware Version

3.0.3
3.1.0

## Introduction

Expand Down Expand Up @@ -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

Expand Down
7 changes: 3 additions & 4 deletions SYSEX_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,18 @@ 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)"

"Here is a new set of device options for you". Payload (other than mfg header, top/tail, etc) of 16 bytes to go straight into appropriate locations of EEPROM, according to the memory map described in `README.md`.

## `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)"
Expand All @@ -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".
39 changes: 28 additions & 11 deletions lib/config.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#include "config.h"
#include "16next.h"
#include "flash_onboard.h"

const uint8_t memoryMapLength = 80;

// default memorymap
// | Address | Format | Description |
// |---------|--------|------------------------------------|
Expand All @@ -18,25 +17,29 @@ 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) |
uint8_t defaultMemoryMap[] = {
// | 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];
uint8_t newMemoryMap[MEMORY_MAP_LENGTH];

// 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
// * 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];
}

Expand All @@ -48,9 +51,9 @@ void updateConfig(uint8_t *incomingSysex, uint8_t incomingSysexLength, Controlle
}

void loadConfig(ControllerConfig *cConfig, bool setDefault) {
// read 80 bytes from internal flash
uint8_t buf[memoryMapLength];
readFlash(buf, memoryMapLength);
// read 86 bytes from internal flash
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)) {
Expand Down Expand Up @@ -83,10 +86,24 @@ 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) {
writeFlash(config, memoryMapLength);
writeFlash(config, MEMORY_MAP_LENGTH);
}

void setDefaultConfig() {
Expand Down
3 changes: 2 additions & 1 deletion lib/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ 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;
extern uint8_t defaultMemoryMap[];

void updateConfig(uint8_t *incomingSysex, uint8_t incomingSysexLength, ControllerConfig *cConfig);
Expand Down
12 changes: 6 additions & 6 deletions lib/sysex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ 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 80 bytes from internal flash
uint8_t buf[80];
readFlash(buf, 80);
// read 86 bytes from internal flash
uint8_t buf[MEMORY_MAP_LENGTH];
readFlash(buf, MEMORY_MAP_LENGTH);

// build a message from the version number...
currentConfigData[0] = DEVICE_INDEX;
currentConfigData[1] = FIRMWARE_VERSION_MAJOR;
currentConfigData[2] = FIRMWARE_VERSION_MINOR;
currentConfigData[3] = FIRMWARE_VERSION_POINT;

// ... and the first 80 bytes of the external data
for (uint8_t i = 0; i < memoryMapLength; i++) {
// ... and the first 86 bytes of the external data
for (uint8_t i = 0; i < MEMORY_MAP_LENGTH; i++) {
currentConfigData[i + 4] = buf[i];
}

Expand Down