RGB rotary encoder full-res spot (walk?) mode feedback

Greetings, all!

I’m about to take delivery of my custom controller (extremely excited!) and start hacking away at my first project. I haven’t picked it up yet, so this request lacks the benefits of hands-on experience; forgive me if I’m missing something obvious!

The Desired Feature

It looks (from the RGB rotary encoder video and from the simpleSpot array in FeedbackClass.h) as though the Spot mode only illuminates a single LED in the ring at once. With up to two adjacent LEDs and six addressable colors, you could precisely represent a full 7-bit value in the ring in a visually progressive manner, where the LEDs and colors sweep sensibly around the ring in exactly 128 possible configurations.

This would be great for custom applications (at least for mine :slight_smile: ) and I think it might work really well for general feedback. Below are some thoughts and a draft proposal. I might try to implement this as a firmware modification exercise, but I thought I’d get the feature request out there first.

I saw an earlier video where rotation steps between one or two illuminated ring LEDs, which is kind of like the feature I’m proposing, but I don’t think that mode is available currently. It looks as though there’s a bitmap table for it in FeedbackClass.h called walk but no corresponding case in feedback.ino, so maybe you’re planning to implement it soon anyway, in which case the only new thing I’m proposing would be the color progressions.

The Proposal

In the firmware, pick six colors (hardcoded, for now) that progress from a low intensity to a high intensity. Call these A, B, C, D, E, F. All that matters is that the colors have a reasonable visual ordering. For example, A through C could be three hues of dark blue, from darkest to lightest; D and E could be two shades of light blue, darker and lighter; and F could be white.

Feedback begins with a 7-bit (0…127) feedback value, either from an internally tracked CC or from an application-sent CC.

Between 0 and 2 LEDs would be active for any given value:

0 - No ring LEDs (obviously!)
1 to 6 - Only the 1st (lowest) ring LED. (This is one of two ranges with six values; the rest have 5).
7 to 11 - Both the 1st and 2nd ring LEDs.
12 to 16 - Only the 2nd ring LED.
And so forth, in groups of five, until:
112 to 116 - Only the 12th ring LED.
117 to 121 - Both the 12th and the 13th ring LED.
122 to 127 - Only the 13th (highest) ring LED. (This is the other range with six values).

Now, use the colors to make the feedback full-resolution (with a different, progressive visual configuration for each feedback value). In each value group where only one LED is lit, assign color to that LED accordingly:

Initial group of 6: 1 through 6 get A through F
Eleven middle groups of 5: 12 through 16 (and 22 through 26, etc.) get B through F (skipping A)
Final group of 6: 122 through 127 get A through F.

In each value group where two LEDs are lit, assign colors to the lower and higher LEDs accordingly, in an inverse relationship, where one visually pours into the other:

7, 17, …: lower LED gets E, higher LED gets A.
8, 18, …: lower LED gets D, higher LED gets B.
9, 19, …: lower LED gets C, higher LED also gets C.
10, 20, …: lower LED gets B, higher LED gets D.
11, 21, …: lower LED gets A, higher LED gets E.

Now we have 1 [zero case] + (2 * 6) [first and last single-LED cases] + (11 * 5) [middle single-LED cases] + (12 * 5) [double-LED cases] = 128 representations for 128 values.

Sketch of an implementation

The ring bits would get set based on the CC value in feedback.ino using something very close to (perhaps identical to) the existing walk array.

The colors would probably get assigned in the SAMD11-NeoPixel main.c, as in the ENCODER_VUMETER_FRAME case. As a hack (to avoid needing to update the serial buffer structure), we can repurpose the RGB values, since (as with the VUMeter) they’re not being used for color information. Rather than passing the CC value and requiring both CPUs to do the same logic, we could pass a more low-level representation of the state using a single byte in (say) sendSerialBufferDec[d_R]:

Lower four bits are the index of the single LED (for the one-LED groups) or the lower LED (for the two-LED groups): 0…12. Unused if the upper four bits are 0000.

Upper four bits are the LED count and color progression combined: 0 meaning no LEDs, 1…6 meaning A through F in the single-LED case, and 7…11 meaning E/A => D/B => C/C => B/D => A/E in the double-LED case.

Obviously, there are other (and maybe more sensible or efficient) ways of passing this to the NeoPixel controller; the point is that there are more than enough unused bits in the buffer to let the SAMD11 code assign the colors directly with minimal logic.

Closing Thoughts

This is probably an unnecessary level of implementation detail given that the proposal itself is quite rough, and I haven’t even mocked it up, but when I close my eyes and imagine it, it looks good :slight_smile: (at least with the right color choice).

My guess is that the most disruptive/discontinuous point in the progression will be the jump from two LEDs to one (e.g. 11 to 12, where we go from AE to _B), because the color of the upper LED is going “down” even as the represented value is going up (or vice versa). There may be a slick way around that, possibly involving the same bitmap assignments but more colors (so that the double-LED cases stick in the midrange and only the single-LED cases get to the brightest colors).

It would probably also be worth tweaking the progression slightly so that 63 or 64 corresponded to the brightest color in the top slot. This would be very easy to do but would complicate the description, so I didn’t write it up that way.

This could all be handled by an application, of course, if you exposed arbitrary velocity-based color table access to the ring LEDs (the way that you now do with digital buttons and with the base LED group on the rotary encoder). (While we’re at it, you could even consider allowing separate access to the three LEDs on the base!) But that’s now a ton of MIDI traffic for feedback, and there’s probably a good reason that the complex color assignment code lives on the SAMD11 anyway.

Anyway, let me know what you think (or if this sounds crazy/too complicated/not a priority, please ignore it and I’ll work up a mockup or demo later!)


Hi @gc3!

Welcome to the forum!

Wow man a ton of information. There’s a bunch of great stuff in this post, I am still trying to digest it all! ahah
I’ll try to address your request and answer the questions as good as possible :slight_smile:

Full resolution for the encoders is something we imagined and even started working on.

As you say, the WALK progression was there and we left it out because the step where 2 LEDs were lit wasn’t really clear as to which was the following state. You couldn’t really tell if you were closer to the previous or next LED, speaking about the current value.

The firmware needs to address many hardware AND functional configurations, and one of the main issues with the full resolution LED signaling, is that this resolution can be reconfigured.

Your proposal seems very doable for a 128 value resolution!

But what if the user configures the encoder to only send values from 5 to 25, or a 14bit message from 2763 to 12432.

This is a bit more complex, and we are sure there is a solution out there that can meet all the possibilities, but we still haven’t found it yet.

The color assignment in the VUMETER case lives in the SAMD11 as a way of simplifying the protocol that the two microcontrollers use to communicate with each other.

If I didn’t get it wrong, you propose to discard the color information in the serial packet and use it to signal the different states, so the color assignment is actually done in the SAMD11.
This could work, like I said, for the 128 value case, and it could be a mode that doesn’t look to the color configuration of the encoder, but just at it’s value.

I tried implementing a progression based only on the brightness of the led, and for that I found that it was necessary to add to the serial packet the current value and the MIN and MAX values. This is possible, but makes even bigger the serial buffer. This compromises mainly the RAM in the SAMD11, for a max-out configuration (32 encoders, 256 digital, no analogs with feedback yet).

And i got it working, only for the FILL mode. For the other modes it got a lot more complex.

Bottom line: We’ll keep working on a way to offer full-resolution that can address all the possible configuration scenarios.

As a custom firmware, you can implement this for the 7 bit case!

Let me know if there is something I missed and didn’t answer or something that is not entirely clear!

1 Like

Thanks again @francoytx! I appreciate your wading through this SYSEx dump of an idea…

I’m mostly focused on the feedback perf thing right now because this is nice-to-have and that’s existential for my first application :slight_smile: but these are all really good points.

For my way of thinking about the full-res display and its usefulness, I actually think it shouldn’t scale. If the controller range is limited, I’d want the same representation, just unable to go below 5 or above 25 (or whatever). That wouldn’t be what I’d expect for all modes, it it would be for this one.

I did think about 14bit as well and I suspect it would be fine in most use cases to round to 7-bit for representation (probably MSB if LSB < 64 else MSB+1–need to think about the endpoints more, though–you’re right, this does get complicated!) Anyone using a rotary encoder is probably not trying to hit exact 14-bit values. If that were needed, then two encoders could be ganged; one MSB, one LSB, and at the upper and lower ends of the MSB range the LSB could be clipped (in case the 14-bit range doesn’t start and end on a multiple of 127). But that’s a pretty obscure need!

If it were important to give more than 7-bit feedback on value changes (but exactness at 14 bits wasn’t important), more shades could be added. Maybe I’ll try that after the 7-bit case and see if it adds any value.

well today if you set MIN and MAX values the LED ring represents (for example with SPOT fb mode) MIN value with no LEDs on, and MAX values with the last LED on.

If I did not get it wrong, you’re saying that 5 and 25 should always have the same ring representations?

I think that could be problematic, for example, if you set an encoder to cycle through a few options in a synth that receives CC values 0-9 or similar, only with the full-res feedback you would notice some changes as you change encoder’s values, and it would stay only in the first LED.
At least for the full-res mode.

It’s a nice idea that could make this implementation easier, for sure!