Can anybody help me with examples of code?
How is the approach to optimize code relative to my example with using "switch case" to "arrays"? Without changing its functionality, but reducing the amount of code by approximately 10 times. How will all this affect the performance of the code for the MIDI protocol?
code description:
This is a Arduino MIDI CC#2SysEx translator program for a synthesizer that only understands SysEx commands. The code translates MIDI CC# values into SysEx according to a given mapping table,
but before running we expose changes to the range of CCvalue (0-127) system to scailed into the value range of target parameter, that the device receiving the final SysEx messages is ready to recieve. For exsample:
//MIDIinCC#hex; targetParam#; Discribtion; targetVal.range; scaling
case 0x04: // param 4 VCF Mode/Slope (2) >>6
case 0x43: // param 67 OSC MG Select (4) >>5
case 0x4A: // param 74 Unison Detune (8) >>4
case 0x1C: // param 28 Vel. VCF Cutoff (16) >>3
case 0x44: // param 68 OSC MG Frequency (32) >>2
case 0x03: // param 3 Noise Level. (64) >>1
case 0x06: // param 6 VCF Cutoff (128) =
...
case 0x3F: // param 63 OSC1 Octave (3) *1/43
case 0x3E: // param 62 Bit DA Resolution (5) *1/26
case 0x42: // param 66 Osc 2 Interval (12) *24/256
case 0x28: // param 40 JStck PBend Range (13) *26/256
case 0x00: // param 0 OSC1 Level (101) *203/256
...
case 0x2e: // param 46 DDL-1 Time (501) *252/64
case 0x34: // param 52 DDL-2 Time (501)
...
//MIDI inplementation: https://dn790004.ca.archive.org/0/items/sm_DSS-1ServiceManual/DSS-1ServiceManual.pdf)
#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();
const int ledPinOut = 13;
const int dssCh = 0;
//only for parameters range 500
void sendDSS1Param_9(byte channel, byte paramNumber, byte paramValue7Bit) {
const int sysexLen = 9;
static byte sysexData[sysexLen] = {0xf0, 0x42, 0x30, 0x0b, 0x41, 0x00, 0x00, 0x00, 0xf7};
paramValue7Bit &= 0x7f;
int paramValueScaled;
switch (paramNumber) {case 0x2e: case 0x34: paramValueScaled = paramValue7Bit*252/64;}
sysexData[2] = 0x30 | ((channel - 1) & 0x0f);
sysexData[5] = paramNumber;
sysexData[6] = paramValueScaled & 0x7f; //LSB
sysexData[7] = (paramValueScaled >> 7) & 0x03;//MSB
MIDI.sendSysEx(sysexLen, sysexData, true);
}
void sendDSS1Param_8(byte channel, byte paramNumber, byte paramValue7Bit) {
const int sysexLen = 8;
static byte sysexData[sysexLen] = {0xf0, 0x42, 0x30, 0x0b, 0x41, 0x00, 0x00, 0xf7};
paramValue7Bit &= 0x7f;
byte paramValueScaled = 0;
//here we rescale to fit to appropriate bit width for each param
switch (paramNumber) {
case 0x04: // param 4 VCF Mode/Slope (2)
case 0x05: // param 5 VCF EG Polarity (2)
case 0x26: // param 38 ATch VCF Mode (2)
case 0x29: // param 41 JStck VCF Mode (2)
case 0x33: // param 51 DDL2 Input Select (2)
case 0x39: // param 57 DDL2 Mod Invert (2)
case 0x3D: // param 61 Sync Mode (2)
case 0x48: // param 72 A.Bend Polarity Mode(2)
paramValueScaled = paramValue7Bit >> 6; break;
case 0x43: // param 67 OSC MG Select (4)
case 0x47: // param 71 A.Bend Select (4)
case 0x4D: // param 77 Unison Voices (4)
paramValueScaled = paramValue7Bit >> 5; break;
case 0x4A: // param 74 Unison Detune (8)
paramValueScaled = paramValue7Bit >> 4; break;
case 0x1C: // param 28 Vel. VCF Cutoff (16)
case 0x24: // param 36 ATch OSC MG Intens (16)
case 0x25: // param 37 ATch VCF Level (16)
case 0x27: // param 39 ATch VCA Level (16)
case 0x2F: // param 47 DDL1 Feedback (16)
case 0x30: // param 48 DDL1 Effect Level (16)
case 0x35: // param 53 DDL2 Feedback (16)
case 0x36: // param 54 DDL2 Effect Level (16)
case 0x3A: // param 58 OSC1 Multisound (16)
case 0x3B: // param 59 OSC2 Multisound (16)
case 0x46: // param 70 OSC MG Delay (16)
paramValueScaled = paramValue7Bit >> 3; break;
case 0x44: // param 68 OSC MG Frequency (32)
case 0x49: // param 73 A.Bend/Porta.time (32)
case 0x4B: // param 75 Vel.OSC X-Switch (32)
paramValueScaled = paramValue7Bit >> 2; break;
case 0x03: // param 3 Noise Level (64)
case 0x07: // param 7 VCF EG Intensity (64)
case 0x08: // param 8 VCF Resonance (64)
case 0x09: // param 9 VCF Kbd Track (64)
case 0x0A: // param 10 VCF MG Frequency (64)
case 0x0B: // param 11 VCF MG Delay (64)
case 0x0C: // param 12 VCF MG Intensity (64)
case 0x0D: // param 13 VCF EG Attack (64)
case 0x0E: // param 14 VCF EG Decay (64)
case 0x0F: // param 15 VCF EG Breakpoint (64)
case 0x10: // param 16 VCF EG Slope (64)
case 0x11: // param 17 VCF EG Sustain (64)
case 0x12: // param 18 VCF EG Release (64)
case 0x14: // param 20 VCA Level (64)
case 0x15: // param 21 VCA EG Attack (64)
case 0x16: // param 22 VCA EG Decay (64)
case 0x17: // param 23 VCA EG Breakpoint (64)
case 0x18: // param 24 VCA EG Slope (64)
case 0x19: // param 25 VCA EG Sustain (64)
case 0x1A: // param 26 VCA EG Release (64)
case 0x1B: // param 27 Vel.A.Bend Intens (64)
case 0x1D: // param 29 Vel.VCF EG Attack (64)
case 0x1E: // param 30 Vel.VCF EG Decay (64)
case 0x1F: // param 31 Vel.VCF EG Slope (64)
case 0x20: // param 32 VCA EG Intensity (64)
case 0x21: // param 33 Vel.VCA EG Attack (64)
case 0x22: // param 34 Vel.VCA EG Decay (64)
case 0x23: // param 35 Vel.VCA EG Slope (64)
case 0x2C: // param 44 DDL MG-A Freq. (64)
case 0x2D: // param 45 DDL MG-B Freq. (64)
case 0x31: // param 49 DDL1 MG-A Intens (64)
case 0x32: // param 50 DDL1 MG-B Intens (64)
case 0x37: // param 55 DDL2 MG-A Intens (64)
case 0x38: // param 56 DDL2 MG-B Intens (64)
case 0x41: // param 65 OSC2 Detune (64)
case 0x45: // param 69 OSC MG Intensity (64)
paramValueScaled = paramValue7Bit >> 1; break;
case 0x02: // param 2 A.B.Intesity/Prt.mix(128)
case 0x06: // param 6 VCF Cutoff (128)
case 0x13: // param 19 VCA Kbd Decay (128)
paramValueScaled = paramValue7Bit; break;
case 0x3F: // param 63 OSC1 Octave (3)
case 0x40: // param 64 OSC2 Octave (3)
case 0x4C: // param 76 Key Assign mode (3)
paramValueScaled = paramValue7Bit/43; break;
case 0x3E: // param 62 Bit DA Resolution (5)
paramValueScaled = paramValue7Bit/26; break;
case 0x42: // param 66 OSC2 Interval (12)
paramValueScaled = paramValue7Bit*24/256; break;
case 0x28: // param 40 JStck PBend Range (13)
case 0x2A: // param 42 EQ Bass (13)
case 0x2B: // param 43 EQ Treble (13)
case 0x3C: // param 60 Max OSC Band Range (13)
paramValueScaled = paramValue7Bit*26/256; break;
case 0x00: // param 0 OSC1 Level (101)
case 0x01: // param 1 OSC2 Level (101)
paramValueScaled = paramValue7Bit*203/256; break;
default: return;
}
sysexData[2] = 0x30 | ((channel - 1) & 0x0f);
sysexData[5] = paramNumber;
sysexData[6] = paramValueScaled;
MIDI.sendSysEx(sysexLen, sysexData, true);
}
void handleNoteOn(byte channel, byte note, byte velocity) {
digitalWrite(ledPinOut, HIGH);//turn the LED on
MIDI.sendNoteOn(note, velocity, channel);
}
void handleNoteOff(byte channel, byte note, byte velocity) {
//Do something when the note is released.
//Note that NoteOn messages with 0 velocity are interpreted as NoteOffs.
digitalWrite(ledPinOut, LOW);//turn the LED off
MIDI.sendNoteOff(note, velocity, channel);
}
void handleProgramChange(byte channel, byte number) {MIDI.sendProgramChange(number, channel);}
void handleAfterTouchChannel(byte channel, byte pressure) {MIDI.sendAfterTouch(pressure, channel);}
void handlePitchBend(byte channel, int bend) {MIDI.sendPitchBend(bend, channel);}
void handleSystemExclusive(byte* arrayData, unsigned arrayLen) {MIDI.sendSysEx(arrayLen, arrayData, true);}
void handleControlChange(byte channel, byte number, byte value) {
if (channel != (dssCh + 1)) {//dssCh=0 is differ&less channel=1
MIDI.sendControlChange(number, value, channel); return;
}
switch(number) {//Map of received CC# to corresponding paramNamb
//Fader group
case 0x00: sendDSS1Param_8(channel, 0, value); break;
case 0x01: sendDSS1Param_8(channel, 1, value); break;
case 0x02: sendDSS1Param_8(channel, 3, value); break;
case 0x03: sendDSS1Param_8(channel, 63, value); break;
case 0x04: sendDSS1Param_8(channel, 64, value); break;
case 0x05: sendDSS1Param_8(channel, 2, value); break;
case 0x06: sendDSS1Param_8(channel, 77, value); break;
case 0x07: sendDSS1Param_8(channel, 20, value); break;
case 0x08: sendDSS1Param_8(channel, 42, value); break;
case 0x09: sendDSS1Param_8(channel, 43, value); break;
case 0x0A: sendDSS1Param_8(channel, 21, value); break;
case 0x0B: sendDSS1Param_8(channel, 22, value); break;
case 0x0C: sendDSS1Param_8(channel, 23, value); break;
case 0x0D: sendDSS1Param_8(channel, 24, value); break;
case 0x0E: sendDSS1Param_8(channel, 25, value); break;
case 0x0F: sendDSS1Param_8(channel, 26, value); break;
//V-Coder group
case 0x10: sendDSS1Param_8(channel, 58, value); break;
case 0x11: sendDSS1Param_8(channel, 59, value); break;
case 0x12: sendDSS1Param_8(channel, 62, value); break;
case 0x13: sendDSS1Param_8(channel, 66, value); break;
case 0x14: sendDSS1Param_8(channel, 65, value); break;
case 0x15: sendDSS1Param_8(channel, 73, value); break;
case 0x16: sendDSS1Param_8(channel, 74, value); break;
case 0x17: sendDSS1Param_8(channel, 75, value); break;
case 0x18: sendDSS1Param_8(channel, 6, value); break;
case 0x19: sendDSS1Param_8(channel, 8, value); break;
case 0x1A: sendDSS1Param_8(channel, 13, value); break;
case 0x1B: sendDSS1Param_8(channel, 14, value); break;
case 0x1C: sendDSS1Param_8(channel, 15, value); break;
case 0x1D: sendDSS1Param_8(channel, 16, value); break;
case 0x1E: sendDSS1Param_8(channel, 17, value); break;
case 0x1F: sendDSS1Param_8(channel, 18, value); break;
//Select SW group
case 0x40: sendDSS1Param_8(channel, 38, value); break;
case 0x41: sendDSS1Param_8(channel, 41, value); break;
case 0x42: sendDSS1Param_8(channel, 61, value); break;
case 0x43: sendDSS1Param_8(channel, 51, value); break;
case 0x44: sendDSS1Param_8(channel, 57, value); break;
case 0x45: sendDSS1Param_8(channel, 72, value); break;
case 0x46: sendDSS1Param_8(channel, 4, value); break;
case 0x47: sendDSS1Param_8(channel, 5, value); break;
// PC control messages (range 500)
case 0x2e: sendDSS1Param_9(channel, 46, value); break;
case 0x34: sendDSS1Param_9(channel, 52, value); break;
#if 0
// Unmapped/reserved Switch list
//Select SW group
case 0x48: sendDSS1Param_8(channel, , value); break;
case 0x49: sendDSS1Param_8(channel, , value); break;
case 0x4A: sendDSS1Param_8(channel, , value); break;
case 0x4B: sendDSS1Param_8(channel, , value); break;
case 0x4C: sendDSS1Param_8(channel, , value); break;
case 0x4D: sendDSS1Param_8(channel, , value); break;
case 0x4E: sendDSS1Param_8(channel, , value); break;
case 0x4F: sendDSS1Param_8(channel, , value); break;
//V-Coder Select group
case 0x50: sendDSS1Param_8(channel, , value); break;
case 0x51: sendDSS1Param_8(channel, , value); break;
case 0x52: sendDSS1Param_8(channel, , value); break;
case 0x53: sendDSS1Param_8(channel, , value); break;
case 0x54: sendDSS1Param_8(channel, , value); break;
case 0x55: sendDSS1Param_8(channel, , value); break;
case 0x56: sendDSS1Param_8(channel, , value); break;
case 0x57: sendDSS1Param_8(channel, , value); break;
case 0x58: sendDSS1Param_8(channel, , value); break;
case 0x59: sendDSS1Param_8(channel, , value); break;
case 0x5A: sendDSS1Param_8(channel, , value); break;
case 0x5B: sendDSS1Param_8(channel, , value); break;
case 0x5C: sendDSS1Param_8(channel, , value); break;
case 0x5D: sendDSS1Param_8(channel, , value); break;
case 0x5E: sendDSS1Param_8(channel, , value); break;
case 0x5F: sendDSS1Param_8(channel, , value); break;
#endif
default: MIDI.sendControlChange(number, value, channel); break;
}
}
void setup() {pinMode(ledPinOut, OUTPUT); digitalWrite(ledPinOut, LOW);
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleNoteOff(handleNoteOff);
MIDI.setHandleControlChange(handleControlChange);
MIDI.setHandleProgramChange(handleProgramChange);
MIDI.setHandleAfterTouchChannel(handleAfterTouchChannel);
MIDI.setHandlePitchBend(handlePitchBend);
MIDI.setHandleSystemExclusive(handleSystemExclusive);
MIDI.begin(MIDI_CHANNEL_OMNI);
MIDI.turnThruOff();
}
void loop() {MIDI.read();}
```
-
\$\begingroup\$ This is a Arduino MIDI CC#2SysEx translator program for a synthesizer that only understands SysEx commands. The code translates MIDI CC# values into SysEx according to a given mapping table, but before running we expose changes to the CCvalue system to scailed the values of parameter that the device receiving the final SysEx messages is ready to respond to. \$\endgroup\$Vladistone– Vladistone2023年12月02日 10:17:08 +00:00Commented Dec 2, 2023 at 10:17
-
\$\begingroup\$ Which model Arduino are you targetting? \$\endgroup\$G. Sliepen– G. Sliepen2023年12月02日 12:14:10 +00:00Commented Dec 2, 2023 at 12:14
-
\$\begingroup\$ Nano; Uno; ATmega328P \$\endgroup\$Vladistone– Vladistone2023年12月02日 12:21:40 +00:00Commented Dec 2, 2023 at 12:21
1 Answer 1
The compiler might already create an array
Compilers are very good at optimizing the code nowadays, and use many tricks. They can in fact convert switch
-statements into various forms, including table based forms. One way to find out what it generates is to look at the generated assembly output, for example on godbolt.org. It turns out that it will create an 78 word long (156 bytes) jump table, corresponding to the maximum parameter number 77. Pretty neat!
You could probably optimize the code size a tiny bit more, knowning that there are only 12 different ways to calculate the scaled value, you could create an array of 78 4-bit values, which would only need 39 bytes, and then add a second array of 12 words that will expand each 4-bit value to the address to jump to. You also need a little bit of extra code to do handle this, so you'd only save ~70 bytes of program space compared to what the compiler generated.
Making the source code more compact
Apart from making the generated code smaller (which the compiler already does for you), maybe you meant making the source code smaller. I think the switch
-statement is already quite compact. You could write your own table like so:
const PROGMEM byte max_value[78] = {
101, // param 0 OSC1 Level
101, // param 1 OSC2 Level
128, // param 2 A.B.Intensity/Prt.mix
64, // param 3 Noise Level
...
};
void sendDSS1Param_8(byte channel, byte paramNumber, byte paramValue7Bit) {
...
paramValueScaled = paramValue7Bit * max_value[paramNumber] / 128;
...
}
That simplifies the function a lot, which I think is preferrable. It doesn't actually reduce the number of lines of code all that much though.
-
\$\begingroup\$ Thank you for the detailed analysis of the code, taking into account its compilation; (But I`dnt understanded nothing by godbolt.org). And I'm even no confused that you didn't describe the possibility of a two-dimensional array for linking an incoming index by paramNumber and target Param map. Is it really better not to spoil things while striving for the best? Ind one question: - I consirning to the fact that the code must use MIDI data - does this affect performance when processing a large amount of MIDI data per time? (Midi Baud Rate 31250) \$\endgroup\$Vladistone– Vladistone2023年12月04日 11:59:52 +00:00Commented Dec 4, 2023 at 11:59
-
\$\begingroup\$ As for not giving you a full example for the two-array solution (which is not the same as a two-dimensional array): we review code here, we don't rewrite the code for you. You will learn more by trying to implement it yourself. Also, I'm lazy :P. As for performance: consider that most Atmega328p instructions take 1 to 3 cycles. The multiply, add and shift instructions only takes 1 cycle. An Arduino Uno runs at 16 MHz, and MIDI is 3125 bytes per second, so you have 5120 cycles per MIDI byte before the performance of your code becomes problematic. \$\endgroup\$G. Sliepen– G. Sliepen2023年12月04日 19:58:49 +00:00Commented Dec 4, 2023 at 19:58