I recently discovered, that my beloved guitar amp sounds so much better without too many FX pedals in the input signal chain. Still, I don’t want to live without a nice chorus or delay. So, additionally to micing the amp’s speaker, I got myself a Palmer PSI 03 JB in order to feed my old tc d-two with a speaker simulated signal and have two additional wet channels.
In order to switch programs on the d-two I’d need to buy myself a midi foot controller, and with that I’d not be able to tap the delay time, as tc states, that there’s no way to do that using MIDI (see here). While doing a bit more research, I found the MIDI specification for my device. I noticed, that you’re able to change the delay time by using either MIDI CC48 or a SysEx command.
So, I had the idea to get myself a nice pedal case, switches, etc. and some Arduino. I read up on how to use it to send MIDI data and founds this wonderful article. So without doing much circuit design or creating a BOM, I went to my local electronics supplier and got the stuff needed, which isn’t much after all:
- aluminium case
- 3 momentary foot switches
- 3 220 Ohm resistors
- 1 DIN/MIDI plug, female
- mounting screws
- Connectors for the arduino
- Green LED
- Arduino UNO
I had some vero board still lying around and decided to use it for the rather small circuit needed to do this. I really hate these boards! All that cutting and scraping. But well, sufficient for now. 😉 I first programmed the tapping using a bread board and the LED and quickly went on with putting the actual case together. It didn’t turn out as nice as I intended it to look, partly owed to my vero board aversion, partly to the fact, that I’m too stupid to create a nice cable routing. 😉 But anyway…
Next up: Trying to understand MIDI data. It took a while until I realized, what 7 and 14 bit packed into bytes is about. Obviously, the most significant bit usually is 1 for status bytes (I’d rather call them command bytes) and 0 for data bytes and the rest of the byte is being used for commands or data. While reading the mentioned MIDI specification for the d-two, I first thought CC48 would do what I wanted. But, how would you encode let’s say 1000ms to a data byte, that on top of that only uses 7 bits for data? The specs don’t tell. 🙁 So I put all my hope in using the “Parameter Data” sysex command:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
0xF0 SysEx 0x00 TC Electronic 0x20 .. 0x1F .. <Device ID> Device ID 0x45 D-Two 0x22 SYXTYPE_PARAMDATA [0x00 | 0x01] 7-bit value specifying system- (1) or algo- (0) parameter. <Param ID> 7-bit Parameter identifier <Data> Byte pair yielding signed 14-bit parameter values (MSB first) 0xF7 EOX Algorithm parameter name ID Min value Max value MIDI_DELAY 0 10000 (5000 using a stereo delay) |
But how the hell do you encode – let’s say – a value of 5000ms to 14 bit divided into an MSB and LSB? Well, I took some paper and started to wrap my brain around bit shifting and masking. I also asked Robin Gareus to verify my method and he kindly pointed out, that I had forgotten handling negative values properly (“Oh, right it’s SIGNED 14 bit!”) and that I had MSB and LSB in the wrong order. So I ended up doing this:
1 2 |
byte dmsb = (d >> 7 ) & 0x3f | (d < 0 ? 0x40 : 0x00); byte dlsb = (d & 0x7f); |
(d is an unsigned int containing the delay time in ms)
Thank you so much, Robin. 🙂
After a few meander regarding the SysEx param IDs, I eventually succeeded:
Well, and here’s the complete code of the sketch. Feel free to use and adapt it. 🙂 It’s probably a bit quick and dirty. Feel free to send me a comment if you have suggestions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
/** * d2ino-midi * * This sketch turns your arduino into a midi controller for program * up/down and lets you tap the delay time via MIDI on the tc d-two. * * created 2016-07-27 * modified 2016-08-01 * by Thomas Ebeling - https://www.bollie.de * * Feel free to use and adapt it. :) */ #define PIN_LED 13 #define PIN_TAP 7 #define PIN_PUP 4 #define PIN_PDN 2 // defaults for user bank #define P_MIN 0 #define P_MAX 127 // Preset unsigned int delay_t = 1000; byte program = P_MIN; // Some states bool led_on = false; bool last_tap_state = HIGH; bool last_pup_state = HIGH; bool last_pdn_state = HIGH; unsigned int start_tap = 0; unsigned int last_led_toggle = 0; /** * Setup serial rate and pin types/states. */ void setup() { // put your setup code here, to run once: pinMode(PIN_LED, OUTPUT); pinMode(PIN_TAP, INPUT_PULLUP); pinMode(PIN_PUP, INPUT_PULLUP); pinMode(PIN_PDN, INPUT_PULLUP); // Set up Serial for midi Serial.begin(31250); } /** * Main Loop. * It listens on the digital pins and checks wether to * send program changes or delay time. */ void loop() { unsigned int ms = millis(); // Collect button states bool cur_tap_state = digitalRead(PIN_TAP); bool cur_pup_state = digitalRead(PIN_PUP); bool cur_pdn_state = digitalRead(PIN_PDN); // Tap handling // Memorize the tap state to avoid flutter if (cur_tap_state == LOW && last_tap_state != cur_tap_state) { if (start_tap == 0) { start_tap = ms; } else if (ms - start_tap > 10000) { start_tap = 0; } else if (start_tap != ms) { // Again, trying to avoid a bit of flutter delay_t = ms - start_tap; // Send the new delay time via sysex midi_set_delay_sysex(delay_t); // Reset start tap time start_tap = 0; } } last_tap_state = cur_tap_state; // Tap LED toggling if (ms - last_led_toggle >= delay_t) { if (led_on) { digitalWrite(PIN_LED, LOW); led_on = false; } else { digitalWrite(PIN_LED, HIGH); led_on = true; } last_led_toggle = ms; } // Program up if (cur_pup_state == LOW && last_pup_state != cur_pup_state) { if (program + 1 <= P_MAX) { midi_set_prog(++program); } } // Program down if (cur_pdn_state == LOW && last_pdn_state != cur_pdn_state) { if (program - 1 >= P_MIN) { midi_set_prog(--program); } } // Memorize the button states last_tap_state = cur_tap_state; last_pup_state = cur_pup_state; last_pdn_state = cur_pdn_state; // Wait an ms delay(1); } /** * Program change * \param p Program to switch to. */ void midi_set_prog(byte p) { Serial.write(0xC0); Serial.write(p); } /** * Sets the delay time for the current patch using tc sysex. * \param d delay time in ms (max. 10000) */ void midi_set_delay_sysex(unsigned int d) { // Do some bit shifting byte dmsb = (d >> 7 ) & 0x3f | (d < 0 ? 0x40 : 0x00); byte dlsb = (d & 127) & 0x7f; Serial.write(0xf0); Serial.write(0x00); Serial.write(0x20); Serial.write(0x1f); Serial.write(0x00); // Device ID Serial.write(0x45); Serial.write(0x22); Serial.write(0x00); Serial.write(0x00); // Param Serial.write(dmsb); // MSB Serial.write(dlsb); // LSB Serial.write(0xf7); // end } |