genuinequality

Download free music MP3s on genuine quality, the world’s largest online music catalogue, powered by your scrobbles. Free listening, videos, photos, The world’s largest online music catalogue, powered by your scrobbles. Free listening, videos, photos, stats, charts, biographies and concerts. stats, charts, biographies and concerts.

Monday, March 17, 2025

Duppa I2C MIDI Controller – Part 2

This is a follow up post to Part 1 where I'm starting to look at MIDI applications and a range of control options. Since posting the first part, I've stumbled across a rather excellent DIY MIDI controller that uses 8 of these Duppa LED rings and…
Read on blog or Reader
Site logo image Simple DIY Electronic Music Projects Read on blog or Reader

Duppa I2C MIDI Controller – Part 2

By Kevin on March 17, 2025

This is a follow up post to Part 1 where I'm starting to look at MIDI applications and a range of control options.

Since posting the first part, I've stumbled across a rather excellent DIY MIDI controller that uses 8 of these Duppa LED rings and 8 endless potentiometers (which I hadn't realised was even a thing!). It is a fantastic build, based on a Teensy and PlatformIO and I totally recommend going and taking a look. Read about it here: https://gerotakke.de/ottopot/.

Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

If you are new to Arduino, see the Getting Started pages.

Parts list

  • Arduino Uno/Nano.
  • DuPPa small RGB LED Ring.
  • Mini I2C Encoder.
  • Bespoke hook-up wires (available from duppa.net).
  • Breadboard and jumper wires.

The Circuit

I eventually want to include various options for controlling the ring and MIDI:

  • Duppa I2C rotary encoder.
  • Plain rotary encoder.
  • Simple potentiometer.
  • Endless potentiometer.

I don't have any endless potentiometers yet, they are something I've only recently found exist, but I'll post again when I get a chance to try them!

The required connections, naturally, are quite different for each case:

  • Duppa I2C encoder: connects to SDA/SCL.
  • Plain rotary encoder: requires two digital IO pins.
  • Simple potentiometer: requires a single analog input.
  • Endless potentiometer: requires two analog inputs.

In this first post, I'm just looking at the same Duppa I2C Encoder and LED Ring that I used in Part 1 but am adding a 5V compatible MIDI module.

The Code

Once again, this is using the Duppa Arduino library: https://github.com/Fattoresaimon/ArduinoDuPPaLib/

CC, Pots, Encoders...

The biggest issue I find with attempting to develop a MIDI CC controller is where is the authoritative definition of what the CC value actually is? What do I mean by that? Well we have the value in the synthesizer, defined on power up or via the on-board controls. And then we have the setting in the external MIDI controller. Until the MIDI controller sends an updated value to the synth, these will be different. And if the value on the synth changes locally, e.g. using knobs or controls on the synth, then they will be out of sync.

I've not found an easy answer to this, but what I'm planning is having the CC controller receive MIDI CC messages as well as send them. This means that if the CC value is changed directly on the synth, if the synth transmits that over MIDI, then it will be received by the external controller which can update its own value accordingly.

One problem with this is that there are two types of hardware controller: relative or absolute.

A rotary encoder is a relative controller - it turns in a direction and the value will increase or decrease accordingly. If the internal knowledge of the CC value changes, the encoder will just continue to increase of decrease from that new value instead.

A potentiometer is (usually) an absolute controller - it turns and has a value. If the internal knowledge of the CC value changes, then unless you have a motorised potentiometer, it will still be in the same place so on turning there will be a jump in value from the stored value to the new setting of the potentiometer.

One option to deal with absolute values is to have the new position value only become relevant once the turning "catches up" with the internal value and the starts adjusting it from that point onwards. But this can create a disjoint between the user experience of turning the knob and it actually changing anything. But on the plus side, absolute values are "remembered" when powered off - providing the knobs are left in the same place.

I'm hoping to use the encoders as a pseudo potentiometer. But it isn't going to be possible to have a complete rotation considered the same as a full sweep of a potentiometer, as that will be down to the number of "detents" per rotation and that won't be anything like enough to support 128 MIDI CC values. But I do plan to indicate the value by LEDS, and use those to indicate the position in the full sweep. This will allow the starting point to change if a CC message is received.

One solution to this problem, and that used by the Ottopot controller mentioned at the start, is to use an endless potentiometer. This not-only allows a variable starting position, but it also represents a full-sweep of values with a single turn as per a simple potentiometer.

So when I get hold of some of those I'll come back to revisit this code.

For this first version there is code for the I2C encoder implemented using the following functions:

  • i2cEncSetup()
  • i2cEncLoop()
  • i2cEncIncrement()
  • i2cEncDecrement()

These are based on the code I used in Part 1. The increment and decrement functions act on a global "midiCC" directly, which stores the CC value to use using the range of a single MIDI "byte" 0 to 127. There is a compilation option to allow wrapping around (between 0 and 127) or not.

MIDI Handler

The Arduino MIDI library is used for both send and receive and all MIDI functionality is wrapped up in the following functions:

  • midiSetup()
  • midiLoop()
  • midiControlChange()

There are a few additional functions to give an optional LED indication of MIDI activity. Within the MIDI loop the midiCC value is checked and if it has changed then a MIDI control change message is transmitted:

void midiLoop() {
MIDI.read();
if (lastMidiCC != midiCC) {
MIDI.sendControlChange(MIDI_CC, midiCC, MIDI_CHANNEL);
}
}

There is an option to have software MIDI THRU enabled and this is handled as part of the MIDI.read() call. On setup midiControlChange() is installed as a handler function for MIDI CC messages and if the correct CC message is received on the correct MIDI channel then the midiCC value is updated directly.

One consequence of using midiCC directly and it being changed by either the encoder or from MIDI is that any change will also trigger the sending of the CC value out too.

This means that if MIDI THRU is enabled and a MIDI CC value is sent to the Arduino then it will almost certainly be sent back over MIDI twice - once as part of the THRU handling, and once as a consequence of it having changed the Arduino's stored midiCC value.

LED Ring Indicator

The simplest implementation will be to scale the 24 LEDs of the ring to the 128 MIDI values and light up the LED that best represents the chosen value.

An alternative is to use one LED per MIDI CC value and allow the ring to loop round, possibly in a different colour. For 128 values across 24 LEDs this means there will be five full circles of the ring plus 8 more.

I've also provided the option to scale the MIDI CC values to a multiple of the LED ring so that the full MIDI 0..127 range wraps around back to 0 back at the start of the ring.

In the following, scalemax is the largest multiple of NUM_LEDS that will fit in 128, then the midiCC value is scaled to that new range and then used in the rest of the LED calculation.

int scalemax = 128 - 128 % NUM_LEDS;
int midiCC_scaled = (midiCC * scalemax / 128);
nextLed = midiCC_scaled % NUM_LEDS;
uint32_t col=0;
if (midiCC_scaled < NUM_LEDS) {
col = 0x00003F;
} else if (midiCC_scaled < NUM_LEDS*2) {
col = 0x003F3F;
} else if (midiCC_scaled < NUM_LEDS*3) {
col = 0x003F00;
} else if (midiCC_scaled < NUM_LEDS*4) {
col = 0x3F3F00;
} else if (midiCC_scaled < NUM_LEDS*5) {
col = 0x3F0000;
} else {
col = 0x3F003F;
}

One quirk to note is that the LEDs are numbered anti-clockwise, so at some point I'll have to reverse the LED number when it comes to presenting an increasing CC value as a clockwise changing display.

I'd also like to have a bit of a lazy, dragging LED change, so I want to implement something that fades illuminated LEDs out as the value changes, leaving some kind of "tail" as the LED moves.

To do this, I've used an array to store the colour value used for any illuminated LEDs and then at regular intervals that colour is updated to fade back to OFF.

I've implemented a relatively simple fade - basically each of the R, G and B components of the colour is bit-shifted to the right by 1 on each "scan". This has the effect of continually dividing the colour value by 2 until it reaches 0. The only thing to watch out for is that I don't do this for the current illuminated LED.

Also note that I only pull out the most significant 7 bits of each 8 bit value (by & 0xFE) so that the shift doesn't map the least significant bit of one value down into the next colour.

for (int i=0; i<NUM_LEDS; i++) {
if (i != nextLed && ledMap[i] > 0) {
uint32_t newcol = ((ledMap[i] & 0xFE0000) >> 1)
+ ((ledMap[i] & 0x00FE00) >> 1)
+ ((ledMap[i] & 0x0000FE) >> 1);
LEDRingSmall.LEDRingSmall_Set_RGB(i, newcol);
ledMap[i] = newcol;
}
}

All the LED handling is wrapped up in the following functions:

  • ledSetup()
  • ledLoop()
  • ledIndex()

The last function is responsible for swapping the LED numbers around to make them go clockwise. It isn't as simple as doing NUMLEDS - led as I still want the first LED to be at "12 o'clock", hence the function to return the correct index.

Find it on GitHub here.

Closing Thoughts

I am so pleased with that lazy LED change effect.

Having so many turns of the encoder isn't particularly practical at the moment, but it does work. It is certainly good to have a few configuration options - especially the option to wrap around, as it takes quite a few turns to get from 0 to 127.

In a follow up post I'll take a look at some of the other options, and when I get my endless encoders in the post that will definitely get a mention too.

I also want to wrap up the existing code somehow to allow for several LED CC controls if required and some kind of box might also be nice.

Kevin

Comment
Like
You can also reply to this email to leave a comment.

Simple DIY Electronic Music Projects © 2025.
Manage your email settings or unsubscribe.

WordPress.com and Jetpack Logos

Get the Jetpack app

Subscribe, bookmark, and get real‑time notifications - all from one app!

Download Jetpack on Google Play Download Jetpack from the App Store
WordPress.com Logo and Wordmark title=

Automattic, Inc.
60 29th St. #343, San Francisco, CA 94110

Posted by BigPalaceNews at 10:21 AM
Email ThisBlogThis!Share to XShare to FacebookShare to Pinterest

No comments:

Post a Comment

Newer Post Older Post Home
Subscribe to: Post Comments (Atom)

Search This Blog

About Me

BigPalaceNews
View my complete profile

Blog Archive

  • August (89)
  • July (96)
  • June (100)
  • May (105)
  • April (95)
  • March (131)
  • February (111)
  • January (104)
  • December (98)
  • November (87)
  • October (126)
  • September (104)
  • August (97)
  • July (112)
  • June (113)
  • May (132)
  • April (162)
  • March (150)
  • February (342)
  • January (232)
  • December (260)
  • November (149)
  • October (179)
  • September (371)
  • August (379)
  • July (360)
  • June (385)
  • May (391)
  • April (395)
  • March (419)
  • February (356)
  • January (437)
  • December (438)
  • November (400)
  • October (472)
  • September (460)
  • August (461)
  • July (469)
  • June (451)
  • May (464)
  • April (506)
  • March (483)
  • February (420)
  • January (258)
  • December (197)
  • November (145)
  • October (117)
  • September (150)
  • August (132)
  • July (133)
  • June (117)
  • May (190)
  • January (48)
Powered by Blogger.