It seems I can't say "CRGB::White %= i", but "CRGB::(255, 255,

It seems I can’t say “CRGB::White %= i”, but “CRGB::(255, 255, 255) %=i” does work. Is that because CRGB::White is a constant? Or is it somehow a different type to CRGB?

Could you give a wee bit more context for the code?

And yes, CRGB::White is a numeric constant, specifically 16,777,215. (Or, more usefully in hex, 0x00FFFFFF.)

CRGB(x,y,z) is a constructor for an instance of the class CRGB.

If you’re trying to make 'White, dimmed down to brightness level ‘i’ ", you could do this:
CRGB w = CRGB::White;
w %= i;

But on the other hand, if it’s only full-on White that you’re dimming down, you could just do this, too:
CRGB w(i,i,i);

for (int i = 0; i < 8; i++) {
CRGB::White %= i;
}

I expected that to give me a succession of greys, but I get a compiler error because it can’t find a %= operator with a left operand of type CRGB constant (something like that, I can’t remember the exact error and I’m on the iPad.) Substituting CRGB(255, 255, 255) works. Given CRGB structs are “trivially copyable” I’m not sure why the compiler needs to make the distinction.

(And shouldn’t the CRGB(255,255,255) evaluate to an instance of a CRGB that is the same type and whose bit pattern is exactly the same as the CRGB::White constant?)

The thing is: your code isn’t doing anything.
It’s not storing the result anywhere.

I mean: %= modifies it’s left hand side. It basically calls .nscale8(…)

So the code you pasted above says something like this (presuming we substitute CRGB(255,255,255) for CRGB::White)
CRGB(255,255,255) %= i;
should be read as:
“Every time we go past here, allocate a brand new temporary CRGB object with no variable name, initialize it to 255,255,255, scale it down by ‘i’, … and then throw it away since it’s now out of scope and wasn’t stored or referenced anywhere.”

What do you want to do with the series of grays? Maybe that’d help me understand what you’re shooting for here.

And again, if you want a ‘gray’ at a given brightness level G (where G is 0…255), you can just do this:
CRGB gray(G,G,G);
// do something with gray here

The same technique also works if you spell gray “grey”.

If CRGB::White is cast to a CRGB object, yes it will result in a bitwise identical object to CRGB(255,255,255).

But before that cast happens, it’s just a 32-bit integer. And %= is a valid, defined operation on integers. (Modulo-in-place.)

So these are absolutely equivalent:
CRGB(255,255,255); // CRGB object
and
CRGB(CRGB::White); // also object

But this is just a number:
CRGB::White
as is
0x00FFFFFF

It is confusing. We made it work so that everywhere you pass a CRGB color as an argument, you can use the defined color name integers. But until you explicitly cast it or pass it as an argument to a function that takes a CRGB object, it’s still just an integer.

Ah yes, sorry, in reality it’s more like:

for (int i = 0; i < 8; i++) {
foo(CRGB::White %= i);
}

(where foo() is some method which takes a CRGB as an argument.)

So you’re saying that this:

for (int i = 0; i < 8; i++) {
foo((CRGB)CRGB::White %= i);
}

Would work?

I think the thing that I keep having an allergic reaction to is calling “modify-this-in-place” on a constant, so yes, if you create an temporary object (by casting, or constructing, from an integer), and then modify the temp obj in place, and then pass that to your function, that’ll work.

I prefer
CRGB(CRGB::White) %= i
over
(CRGB)CRGB::White %= i
even though they do exactly the same thing, as the first reads to me as “construct a scratch CRGB object from this initial value (and then I’m going to modify it)”, and the second reads to me as “cast this constant value to a CRGB object (which I’m then going to modify)” which is a little less clear in intent.

Also, neither of these will be as fast as just constructing the object you want in the first place, e.g.
CRGB(i,i,i)

The machine code for the first way is equivalent to this:
// CRGB temp( CRGB::White);
temp.R = 255;
temp.G = 255;
temp.B = 255;
// temp %= i;
temp.R = (temp.R * i) >> 8;
temp.G = (temp.G * i) >> 8;
temp.B = (temp.B * i) >> 8;

The machine code for the second way is equivalent to this:
// CRGB temp(i,i,i);
temp.R = i;
temp.G = i;
temp.B = i;

BUT! If the first way is clearer to you, then stick with that!

(Of course, looking at this, I also really want to suggest using a color palette, but maybe just because it’s the flavor of the week…)

Heh. Yeah, my mind reads “CRGB::White %= i” as an expression, which has a value, which I ought to be able to pass as an argument.

I’ll look into the palette stuff. Having thought about it, what I was trying to do in the first place was a bit naff anyway and I’m probably going to go with some variation of your palette example code in the end. But confusion over what exactly “CRGB::White” is, has bitten me in the past.

Oh don’t get me started on what’s an expression in C++ and what (stupidly) isn’t, e.g. {…}

#knightsofthelambdacalculus

http://microscheme.org :wink:

Next project will be a compiler for a declarative, timeline-based language for interactive LED programming.

Or AVR back end for LLVM.

Or both at once :wink:

:slight_smile:

I’ve mentioned it before, but I’d love to see Swift on the Arduino. At least somewhat within the realms of possibility, if I understand correctly.

Sure!
Swift, as implemented today, has a bit too much per-function-call overhead (to preserve the dynamism) for my taste for low-level MCU coding like Arduino. (See https://github.com/rodionovd/SWRoute/wiki/Function-hooking-in-Swift )

Dan and I studiously avoid anything “C++ virtual” in FastLED for exactly the same reason; if a simple function call costs you pointer indirection through a global table, everything is going to be a little slower everywhere. Swift (and ObjC)'s dynamic dispatch is even a bit heavier.

BUT, if the compiler can take as given that there is NO code that will run anywhere other than exactly what it has been given (as IS the case on Arduino!), the possibility exists for it to ‘snap’ the indirections into direct jumps. Of course, that requires the sort of advanced control flow, data flow, and pointer aliasing analysis that is generally difficult to come by … in the wild.

Dan and I get to think about these things as part of our so-called “day job”, which is a hell of a lot of fun.