Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update math functions to C++ standard library #7685

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

rubiefawn
Copy link
Contributor

This updates usages of sin(), cos(), tan(), pow(), exp(), log(), log10(), sqrt(), fmod(), fabs(), and fabsf(), excluding any usages that look like they might be part of a submodule or 3rd-party code. There's probably some math functions not listed above that haven't been updated yet.

I haven't touched any of the following items. If these should be updated, I can do that:

  • plugins/CarlaBase/*
  • plugins/LadspaEffect/*
  • plugins/ZynAddSubFx/*
  • src/3rdparty/*
  • Any file matching *test*

Note

Updating these function usages was brought up in #7558, somewhat related, probably not important.

This updates usages of sin, cos, tan, pow, exp, log, log10, sqrt, fmod, fabs, and fabsf,
excluding any usages that look like they might be part of a submodule or 3rd-party code.
There's probably some std math functions not listed here that haven't been updated yet.
lmao one always sneaks by
Copy link
Contributor

@Rossmaxx Rossmaxx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took a bit to review this, and here it is.

Also i could only grab a few cases where pow -> exp2 /fastPow10f is applicable. There's lots more you can change. Would appreciate if you did.

plugins/Compressor/Compressor.cpp Outdated Show resolved Hide resolved
plugins/Compressor/Compressor.cpp Outdated Show resolved Hide resolved
plugins/Eq/EqCurve.cpp Outdated Show resolved Hide resolved
plugins/Eq/EqCurve.cpp Outdated Show resolved Hide resolved
plugins/Eq/EqFilter.h Outdated Show resolved Hide resolved
plugins/SpectrumAnalyzer/SaProcessor.cpp Outdated Show resolved Hide resolved
src/core/AutomationClip.cpp Outdated Show resolved Hide resolved
src/core/LadspaManager.cpp Show resolved Hide resolved
src/core/LadspaManager.cpp Show resolved Hide resolved
src/gui/editors/AutomationEditor.cpp Outdated Show resolved Hide resolved
rubiefawn and others added 5 commits February 3, 2025 23:51
- std::pow(2, x) -> std::exp2(x)
- std::pow(10, x) -> lmms::fastPow10f(x)
- std::pow(x, 2) -> x * x, std::pow(x, 3) -> x * x * x, etc.
- Resolve TODOs, fix typos, and so forth

Co-authored-by: Rossmaxx <74815851+Rossmaxx@users.noreply.github.com>
I mistakenly introduced a bug in my recent PR regarding template
constants, in which a -1 that was supposed to appear outside of an abs()
instead was moved inside it, screwing up the generated waveform. I fixed
that and also simplified the function by factoring out the phase domain
wrapping using the new `ediv()` function from this PR. It should behave
how it's supposed to now... assuming all my parentheses are in the right
place lol
What else is lmms::numbers for?
Copy link
Contributor

@Rossmaxx Rossmaxx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is left

include/lmms_math.h Outdated Show resolved Hide resolved
include/lmms_math.h Outdated Show resolved Hide resolved
rubiefawn and others added 2 commits February 4, 2025 01:27
Co-authored-by: Rossmaxx <74815851+Rossmaxx@users.noreply.github.com>
Co-authored-by: Rossmaxx <74815851+Rossmaxx@users.noreply.github.com>
Copy link
Contributor

@Rossmaxx Rossmaxx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Rossmaxx Rossmaxx requested a review from messmerd February 4, 2025 08:29
@rubiefawn
Copy link
Contributor Author

Based off the myriad of link errors in the CI, it appears that functions defined in headers are not inline by default after all. I'll fix it tomorrow, for now I sleep

@Rossmaxx
Copy link
Contributor

Rossmaxx commented Feb 4, 2025

I thought they were inlined. Sorry @rubiefawn for confusing you.

@LostRobotMusic
Copy link
Contributor

Only class member functions are marked as inline by default. All others aren't. What file it's in doesn't matter.

(Crucially, "marked inline by default" does not mean "inlined by default", the compiler will happily ignore your request if it thinks it knows better.)

For functions, constexpr implies inline so this just re-adds inline to
the one that isn't constexpr yet
include/lmms_math.h Outdated Show resolved Hide resolved
include/lmms_math.h Outdated Show resolved Hide resolved
plugins/Lb302/Lb302.cpp Outdated Show resolved Hide resolved
plugins/Organic/Organic.cpp Outdated Show resolved Hide resolved
plugins/Organic/Organic.cpp Outdated Show resolved Hide resolved
src/core/DrumSynth.cpp Outdated Show resolved Hide resolved
w = std::sin(p);
break;
case 1: // sine^2
w = std::abs(2.f * std::sin(.5f * p)) - 1.f;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starting with the original:

static_cast<float>(fabs(2.0f * static_cast<float>(sin(fmod(0.5f * ph, TwoPi))) - 1.f));

I've tested that the sin(fmod(0.5f * ph, TwoPi)) part is equivalent to:

std::sin(ph / 2)

Plugging that back in, we get:

Suggested change
w = std::abs(2.f * std::sin(.5f * p)) - 1.f;
w = std::abs(2 * std::sin(ph / 2) - 1);

I've confirmed that this result is identical to the original.

Copy link
Contributor Author

@rubiefawn rubiefawn Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "original" referenced as well as your suggested change have the - 1 inside the abs(), which is actually an error I accidentally introduced in my last PR introduced in b2f2fc4. The changes I made here fix that, and reference the original implementation (from
before b2f2fc4 screwed it up). Here are the graphs of both; note that the incorrect version has a range of [0, 3] while the correct one has a range of [-1, 1]:
image

src/core/DrumSynth.cpp Outdated Show resolved Hide resolved
Comment on lines 185 to 186
w = p * 2.f * numbers::inv_pi_v<float>;
if (w > 1.f) { w = 2.f - w; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
w = p * 2.f * numbers::inv_pi_v<float>;
if (w > 1.f) { w = 2.f - w; }
w = std::abs((2 / numbers::inv_pi_v<float>) * std::remainder(ph, 2 * numbers::inv_pi_v<float>)) - 1;

I found this function which works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::remainder is not the same as Desmos's mod. In Desmos it would be x - round(x / y) * y.

When you do that, you'll get a triangle wave. However, after testing it some more, both your triangle wave and mine appear to be off from the original. I'll look into it some more and see what I can find out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated graph; thanks for pointing out the difference between mod() and std::remainder().

image

if (w > 1.f) { w = 2.f - w; }
break;
case 3: // sawtooth
w = p * numbers::inv_pi_v<float> - 1.f;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
w = p * numbers::inv_pi_v<float> - 1.f;
w = (ph - (2 * numbers::inv_pi_v<float>) * static_cast<int>(ph / (2 * numbers::inv_pi_v<float>))) / numbers::inv_pi_v<float> - 1;

I think the previous equation was pretty good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused; the previous equation is not what is suggested here?
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated graph with new code changes:
image

@rubiefawn
Copy link
Contributor Author

rubiefawn commented Feb 5, 2025

Regarding several of the DrumSynth suggested changes, what is the benefit to using std::remainder() over std::fmod() when the former doesn't wrap negative inputs to positive outputs (the triangle waveform, for example, is only valid with input domain [0, τ])? If std::remainder() is significantly faster, then I'm down to use it, I'll just have to prove the other code can't call waveform() with negative phases. Updated to not use std::fmod() either

rubiefawn and others added 4 commits February 4, 2025 22:51
Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com>
Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com>
No ediv(), no std::fmod(), no std::remainder(), just std::floor().
It should all work for negative phase inputs as well. If I end up
needing ediv() in the future, I can add it then.
This reuses more work and is also a lot more easy to visualize.

It's probably a meaningless micro-optimization, but it might be worth changing it back to a switch-case and just calculating ph_tau and saw01 at the beginning of the function in all code paths, even if it goes unused for the first two cases. Guess I'll see if anybody has strong opinions about it.
// sine
if (form == 0) { return std::sin(ph); }
// sine^2
if (form == 1) { return 2.f * std::abs(std::sin(0.5f * ph)) - 1.f; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (form == 1) { return 2.f * std::abs(std::sin(0.5f * ph)) - 1.f; }
if (form == 1) { return std::abs(2.f * std::sin(0.5f * ph) - 1.f); }

If ph==0, your version would output -1 while the old function would output +1.

Here's what I've been using to test my functions:
https://gist.github.com/messmerd/5ee2e0078b574b9878bdd7224192531e

I've added your functions there too for comparison

Copy link
Contributor Author

@rubiefawn rubiefawn Feb 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If ph==0, your version would output -1 while the old function would output +1.

Yes, and the old function wasn't supposed to. It's an error introduced in b2f2fc4; see the graphs of the functions: #7685 (comment)

Consider the original code prior to b2f2fc4:

// original code
case 1: w = (float)fabs(2.0f*(float)sin(fmod(0.5f*ph,TwoPi)))-1.f;

// delete casts, it's all floats anyways
case 1: w = fabs(2.f * sin(fmod(0.5f * ph, TwoPi))) - 1.f;

// remove unnecessary fmod(), sin() is well-defined for inputs outside [0, τ]
case 1: w = fabs(2.f * sin(0.5f * ph)) - 1.f;

// convert c-style math functions to std::* c++ counterparts
case 1: w = std::abs(2.f * std::sin(0.5f * ph)) - 1.f;

// new code
if (form == 1) { return 2.f * std::abs(std::sin(0.5f * ph)) - 1.f; }

// new code, fixed for consistency
if (form == 1) { return std::abs(2.f * std::sin(0.5f * ph)) - 1.f; }

So my "new" version is in fact identical to the original, intended version, with the exception of the 2.f * being outside the abs() rather than inside it. This was an unintentional change, but has no effect on the result in this case. I'll move it back inside to make it match the original better for a cleaner diff history though. (Edit: that has now been done)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants