One Line to Skip Them All - One Line to Find Them

This may sound really elementary to those who are fluent in C++, but since I’m not fluent at all, I’ll ask the question…

I’d like to add one extra feature to my prop where pressing and holding the AUX skips to the first preset (the home font) if pointing up, but the last preset (in this case a charging preset) when pointing down.

Doing just the button press itself is easy enough, like this, where 0 is the number of the first font:

//  Skips to first preset:
#if NUM_BUTTONS == 2
  case EVENTID(BUTTON_AUX, EVENT_FIRST_HELD_LONG, MODE_OFF):
#endif
    if (!mode_volume_) {
      SetPreset(0, true);
    }
#ifdef SAVE_PRESET
    SaveState(current_preset_.preset_num);
#endif
    return true;

But I’ve been trying to adapt a line that NoSloppy kindly did for me a while back to distinguish between hilt pointing up or hilt pointing down in order to build it into the above command.
This line is intended to skip forwards or backwards five presets, and works very well:

SetPreset(current_preset_.preset_num + (fusor.angle1() < -M_PI / 4 ? -5 : 5), true);

But that obviously takes the preset you’re on as a starting point and just moves backwards or forwards five presets from there.

So assuming I have 30 presets on a hilt, is there an easy way to specify - UP goes to preset 0, DOWN goes to preset 29?

And out of interest, what does the 4 mean in the line above? I figured out that -5 means back five presets and 5 means forward five presets, but I couldn’t figure out what the 4 does.

Sincere thanks as always.
:slight_smile:

EDIT: I’m realizing the way I explain the angles (as I was thinking of it) doesn’t make sense given the presented usage of fusor.angle1(). It won’t matter for this explanation as it regards the code and result, but affects specifically my comment about strict “up,” so I’d love for someone who knows that function to chime in.

ProffieOS isn’t quite my thing, but C++ is, so I can dissect this line for you :slight_smile:

Let’s start by breaking this out a little bit, because this uses what’s known as a “ternary” operator. It’s basically an “inline” if-statement. The ? and : separate it out, so that what’s before the ? is basically the if, what’s after the question mark is the true case, and the : is the else case:

if (fusor.angle1() < -M_PI / 4) {
    SetPreset(current_preset_.preset_num + (-5), true);
} else {
    SetPreset(current_preset_.preset_num + 5, true);
}

Now what’s going on should be a bit more clear. We’re checking fusor.angle1() to see if it’s less than negative PI divided by 4, which is negative 45 degrees. What exactly fusor.angle1() returns relative to the hilt’s physical angle I’m unsure, but I assume it to be something of an absolute -PI to PI (-180 to 180, where 0 is horizontal) to describe the hilt rotation, so really here we’re checking if it’s within what constitutes down, not so much checking for specifically “up.”

Now from here, it should be pretty clear why we’re going back and forth 5 from the current preset, it’s because current_preset_.preset_num is getting 5 (or negative 5) added to it! So it’s pretty trivial to instead set the value we want, say, for example, 0 or 29, like so:

if (fusor.angle1() < -M_PI / 4) {
    // If pointing down
    SetPreset(29, true);
} else {
    SetPreset(0, true);
}

Now, what about making this preset-number-agnostic? Well, based on how NoSloppy’s code bit works (this is without doing any prop research, so take it with a little salt), it stands to reason that going over or under the preset range simply loops back around (think if we were at preset 0 and subtract 5, if that works without error, then we can do the following).

So, we could simply do:

if (fusor.angle1() < -M_PI / 4) {
    // If pointing down
    SetPreset(-1, true);
} else {
    SetPreset(0, true);
}

When we’re pointing down, we SetPreset to -1, which, following the above logic, should loop back around to the very last preset, whatever number that may be.

So now we have our solution, let’s make it a neat little one-liner again:

A ternary is like a function that returns whatever value you give it (That means both the true and false case must be the same type), in our case that means it’s kinda like a function:

int ternary(bool caseToEvaluate, int returnIfTrue, int returnIfFalse)

But instead it’s written like:

[Case To Evaluate] ? [Return if true] : [Return if false]

So, our case to evaluate is “If blade is pointing down,” which is the fusor.angle1() < -M_PI / 4
(Btw, it’s worth noting if you want strict angles for “up,” by this logic fusor.angle1() > (3 * M_PI) / 4, there’s more “clean” ways to represent that, but it keeps it logically understandable)

We can put this whole ternary “function” inside the SetPreset function, in fact, that’s what ternaries are largely for, compact little if/else’s.

So, if our case is true (and we are pointing down), we want to give -1 to SetPreset’s first argument, otherwise we will give it 0:

SetPreset((fusor.angle1() < -M_PI / 4) ? -1 : 0, true);

And then tack the true on for the second argument. Note I’ve put the boolean expression (the part that would logically be inside an if) inside parentheses, this is just to make sure the compiler doesn’t get confused and interpret things wrong, and it might not even be necessary, but I like to keep it, since it either prevents problems or does absolutely nothing :slight_smile:

So there you go, if you have any questions, feel free to ask. There’s always a chance I made a mistake, so if something’s confusing, that may be why, and I’m not familiar with the specifics of the inner-workings of ProffieOS, I’m just going based off what I infer, so if Fredrik or Brian want to chime in to correct or clarify usage here, please do!

2 Likes

I think you have it mostly right, except the range is -PI/2 to PI/2 (radians).

One thing though, don’t we want a “dead zone” between up and down where nothing happens? Sort of like this:

if (fusor.angle1() < -M_PI / 4) {
    // If pointing down
    SetPreset(-1, true);
} else if (fusor.angle1() > M_PI / 4) {
    // If pointing up
    SetPreset(0, true);
}
2 Likes

Awesome, that makes sense!

Likely, that’s kind of what I was hinting at with a “strict up,” but I guess it depends on what @Sabersense is after.

1 Like

O M G !!! :open_mouth: :open_mouth: :open_mouth:

You guys… I can’t tell you… just… wow! This is amazing! Thank you both so much! :pray: :pray: :pray:

Not only is it amazing, but it’s even better than that, because if I’ve understood it right, Ryryog, your cunning use of -1 as a preset destination presumably means that this would work regardless of how many presets are in the config? In other words, pointing up will always go to the first font, pointing down will always go to the last one? I’d assumed I would need to tweak the prop for each saber depending on how many presets it had, but your scheme makes that unnecessary! :muscle: :clap:

And I can see the logic of the 4 now as well! Again, thanks so much! :pray:

This is awesome stuff guys! I’m so grateful! :pray:

And Prof, I can see what you mean about having a dead zone at, say, plus or minus 5 degrees, though I can’t quite see what’s different about your last bit of code to make it happen. But we’re getting into serious levels of refinement with that little detail. For now, the stuff you and Ryry have come up with ticks all the boxes for me. :+1: :smiley:

Can’t wait to get it loaded up and have a play! :smiley:

Sincere thanks again! :pray:

https://y.yarn.co/2a1bc618-4e71-4cbe-abf2-3716d8de2fde.mp4?_gl=1*113u8vi*_ga*NTA5ODU4MDQwLjE3MTQwNzc4NDI.*_ga_VB8JQS4PTK*MTcxNDA3Nzg0MS4xLjEuMTcxNDA3ODAzNi4xMC4wLjA.

1 Like

Correct.

As Fredrik described it, the range for fusor.angle1() is -PI / 2 (-90 degrees) to PI / 2 (90 degrees).

So, When pointing down if the angle is less than -45 degrees, the code snippet I provided would consider that as down, and everything else as up.

With the code snippet Fredrik provided, he’s simply making the “else” more strict, so that it must be greater than 45 degrees to be considered up. His lines of code would replace the one I provided, should you want to use it.

EDIT: If you wanted to change the range, you’d just want to change the M_PI / 4 to whatever angle you want.

1 Like

I was thinking more like +/- 45 degrees.
Basically “up” would do one thing, “down” would do another, and straight out would do something else, or possibly nothing.

If all you want is up or down with no dead zone, then you can just do:

if (fusor.angle1() > 0) {
   // up
} else {
  // down
}

Maybe do it like this to make it easier to read:

#define DEGREES_TO_RADIANS (M_PI / 180)
 if (fusor.angle1() > 45 * DEGREES_TO_RADIANS) {
   // up
 } else if (fusor.angle1() < -45 * DEGREES_TO_RADIANS) {
   // down
 } else {
   // not up or down
}

It’s then easy to change it to whatever degrees you want.

2 Likes

Didn’t know you had that in there, neat :slight_smile:

I don’t, that’s why I put it in the code above…

:man_facepalming:
And the daily blind award goes to… @ryryog25!!!

I shouldn’t start these things as each idea tends to promote more ideas (usually prompted by comments and observations from you guys) and I soon get carried away… LOL!

But what if we had UP goes to first preset, DOWN goes to last preset and HORIZONTAL goes to middle preset? I’m guessing it would need to look something like this:

case EVENTID(BUTTON_AUX, EVENT_HELD_LONG, MODE_OFF):

#define DEGREES_TO_RADIANS (M_PI / 180)
 if (fusor.angle1() > 45 * DEGREES_TO_RADIANS) {
   // up
SetPreset(0, true);

 } else if (fusor.angle1() < -45 * DEGREES_TO_RADIANS) {
   // down
SetPreset(-1, true);

 } else {
   // horizontal
SetPreset(15, true);
}

The downside of course is that we need to know how many presets are in the config and then edit the number in the line above from the prop to suit, which is obviously a bit clunky as that number will change from config to config. But for me that’s no a big deal as this is only intended for my installs, not universal use in the wider OS.

So I guess my question is, am I thinking along the right lines and will the above work?
Will try it when I get home today and report back. :slight_smile:

It’s possible to work this out by loading preset -1 into a temporary variable and then check what number you got.

Like this:

  CurrentPreset tmp;
  tmp.SetPreset(-1);
  SetPreset(tmp.preset_num / 2, true);
2 Likes

:open_mouth:
Is there anything you can’t make this code do? :clap: LOL!

Lost in admiration and gratitude as always Prof. Looking forward to trying this out later today. Amazing stuff! :pray:

case EVENTID(BUTTON_AUX, EVENT_HELD_LONG, MODE_OFF):

#define DEGREES_TO_RADIANS (M_PI / 180)
 if (fusor.angle1() > 45 * DEGREES_TO_RADIANS) {
   // up
SetPreset(0, true);

 } else if (fusor.angle1() < -45 * DEGREES_TO_RADIANS) {
   // down
SetPreset(-1, true);

 } else {
   // horizontal
  CurrentPreset tmp;
  tmp.SetPreset(-1);
  SetPreset(tmp.preset_num / 2, true);
}

My C++ is a bit rusty, but is:
> current_config->num_presets
not available?

It is, but it’s not equal to the number of actual presets if someone uses edit mode or the workbench to edit their presets.

Ah. Makes sense–are they stored in an array that gets resized? that was what I went looking for when I found the pointer to num_presets… something like:
sizeof(presets_array)

sizeof is a compile-time operator, and arrays cannot be resized once they’re created. (You can’t get the size of a dynamically allocated array either, not like that at least), so while the presets array could have sizeof called on it (though I’m not sure what exactly you’ve found comes from), that’s certainly not how the presets are stored for runtime.

Keeping track of the number of presets/size of the array would require special handling, which (presumably in this case) happens somewhere else in whatever data structure manages all of them.

Also, based off your (proposed) usage, it’s worth mentioning that sizeof returns the size of the input in bytes, not in number of elements. If you want the number of elements (irrelevant for this here because we’ve already established it’s not useful, but I’ll explain anyways), you’ll have to divide by sizeof whatever type the array is storing, a Preset iirc

EDIT: sizeof operates on the type held (thus why it’s able to be compile-time). In C (and C++) arrays are kind of weird, because their size is part of their type, which allows usage of sizeof, but they can also decay to pointers, which makes things conceptually confusing (because a pointer is just, typically, the native integer size… for the stm32l4 that’s a int32_t). To that end, the reason you can’t use sizeof on a dynamically allocated array is because creating a dynamic array looks like int8_t *arr = new int8_t[SIZE];, you’re simply storing a pointer to the allocated memory, so sizeof will just return the size of the pointer in bytes, so typically 4.

Something of a tangent but I think it’s all relevant.

1 Like

To add to this, let me explain what happens when you do this:

CurrentPreset tmp;
tmp.SetPreset(-1);
SetPreset(tmp.preset_num / 2, true);

CurrentPreset is a class that knows that presets may be loaded either from the preset array, OR from a file on the SD card. So when you call SetPreset() it checks if the relevant files exists, and if so, reads it from that file. It reads one entry at a time and keeps count as it goes. The whole array is never read into memory.

If the file doesn’t exist, then it loads it from the preset array, and in that case current_config->num_presets is also the right answer.

1 Like

Thanks for clarifying–as I said, my C++ is rusty. I mostly code in SQL and VBA these days.

1 Like

I’ll admit that these coding details do go over my head somewhat, but what I can say is I tested the new feature last night and it worked a treat! :smiley:
Thanks so much guys - this will be included in all my installs going forward. :slight_smile: