-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add Frequency Shifter effect (not a pitch shifter) #8140
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
base: master
Are you sure you want to change the base?
Conversation
regulus79
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried it, and it's very clean. Excellent work!
I don't understand how the Hilbert Transform works. I will have to ponder this.
It would be awesome if there were some comments explaining the code.
| const bool doHarm = (harmonics > 0.f); | ||
| const float harmFactor = harmonics * 20.f + 1.f; | ||
| const float harmDiv = 1.f / (harmonics * 0.3f + 1.f); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do the numbers 20 and 0.3 come from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They're completely arbitrary numbers for scaling the Harm parameter to a convenient range. They're in this location of the code because they can be calculated outside of the loop to reduce CPU usage (since we're calculating it 1/256 as often). Here's a Desmos graph that shows what's going on: https://www.desmos.com/calculator/cwzhqb6eis
| const float xc = std::clamp(harmFactor * cosP, -3.f, 3.f); | ||
| const float xs = std::clamp(harmFactor * sinP, -3.f, 3.f); | ||
| const float xc2 = xc * xc; | ||
| const float xs2 = xs * xs; | ||
| const float tc = xc * (27.f + xc2) / (27.f + 9.f * xc2); | ||
| const float ts = xs * (27.f + xs2) / (27.f + 9.f * xs2); | ||
| cosP = std::lerp(cosP, tc * harmDiv, harmonics); | ||
| sinP = std::lerp(sinP, ts * harmDiv, harmonics); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also curious why -3 and 3 are used for clamping. Also, what is the meaning of xc * (27.f + xc2) / (27.f + 9.f * xc2)? Looking at it in desmos, it seems it warps the (cos,sin) circle a bit to become more of a squircle, but it's difficult for me to understand exactly what it is doing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I sent a Desmos graph in another comment. You can set "s=z" to see what would happen without the clamping.
|
Haeleon found a bug with the LFO stereo phase not functioning properly with 0 Spread due to some obsolete resync behavior, that's been fixed now. Also, the Reset buttons don't work, but that's due to an LMMS bug with untoggleable buttons which I fix at #8144. I already had it fixed in my own build, but forgot to carry it over into the official branch. |
rubiefawn
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly pitching in my 2¢ regarding future GUI work, but also left a few comments on code as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For context, what I'm about to say regarding the GUI here should be considered entirely non-blocking; users deserve access to this plugin as soon as possible. I just want to start the discussion sooner than later, since it will be relevant eventually.
There's an effort to change raster GUI elements to vector where possible, detailed in #7767. This includes removing baked-in text and replacing it with dynamically rendered labels, which allows them to be translated, and also includes making layouts responsive instead of using hard-coded coordinate locations, since translated text may be significantly longer (or shorter) than their English counterparts.
Thinking ahead about how this UI will have to change to accommodate all that, I have a few notes:
- Custom fonts except for artistic branding are currently out of the question. I believe it is technically possible for us to bundle additional fonts with LMMS, but it's not currently being considered. Be aware this plugin will almost certainly use the default font in the future.
- Hard-coded locations for UI elements such as knobs will need to be changed to dynamic layouts in the future to accommodate translation strings of various lengths. Be aware this plugin may have UI elements laid out differently once this is done.
- Buttons such as "Anti-reflect" will be changed to be pure CSS where possible as well due to aforementioned text stuff. This will really only affect the "Reset" buttons, since their unusual slanted left-hand edge will probably have to go. Everything else about the buttons should look more or less identical.
- The "Reset" buttons ought to be labelled "Reset phase" since that's what they do; based off the name and position alone, I thought they must reset the controls in their sections. I won't be the last to make this mistake, and there will exist users that rather than read the manual will instead complain about about "broken buttons" on forums and Discord.
help_on.pngandhelp_off.pngalso exist in your Slew Distortion plugin. It may be worth moving these to the default theme and having both Slew Distortion and this plugin reference that common asset instead.- There may be more, but this is what I can immediately think of. I will update this comment as needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's worth noting that the Reset buttons come with tooltips, along with most other things in the plugin, so opening the manual wouldn't be mandatory for that. Also, the help images in this one are very different from the ones in Slew Distortion, though it might be able to get away with using the same ones regardless.
Not a pitch shifter.

(GUI by Haeleon)
Frequency Shifter (not a pitch shifter).
It isn't a pitch shifter.
While "frequency" refers to Hz, "pitch" refers to octaves, semitones, cents, etc..
So, pitch shifting impacts all partials in the audio multiplicatively, while frequency shifting (not pitch shifting) impacts it additively.
For example: If you have frequencies 100, 200, and 300 Hz, a pitch shift upward by 1.2x would result in 120, 240, and 360 Hz. Meanwhile, a frequency shift (not a pitch shift) upward by 20 Hz would result in 120, 220, and 320 Hz.
Notice that a pitch shifter preserves the harmonic relationships between these frequencies, while frequency shifting (not pitch shifting) destroys them entirely, resulting in an inharmonic sound.
This frequency shifter (not a pitch shifter) sports a unique "anti-reflect" algorithm which eliminates all frequency aliasing through Nyquist and 0 Hz.
A frequency shifter (not a pitch shifter) can also be used as a "barberpole phaser". This is similar to other phasers, but unlike those, it can audibly move upward or downward infinitely, similar to a Shepard tone.
To achieve this, simply set the frequency shift (not a pitch shift) amount to your desired phaser rate, and set the Mix to 50%. The resulting phase cancellation will filter the audio.
You may also achieve this by simply increasing the delay feedback, and keeping the delay length very low.
This plugin may also be used as a ring modulator via the RING parameter. Ring modulation is the result of frequency shifting (not pitch shifting) the audio upward and downward by the same amount in parallel.
Frequency Shifter (not a pitch shifter):
Mix - Blends between the wet and dry signals.
Frequency Shift - The amount of frequency shifting (not pitch shifting), in Hz.
Spread - Offsets the frequency shift (not a pitch shift) amount in opposite directions for the left and right channels.
Even very small amounts will add a lot of stereo width to the signal.
Phase - Gives you manual control over the phase of the frequency shifter's (not a pitch shifter) internal oscillators.
When using the frequency shifter (not a pitch shifter) as a barberpole phaser, I highly recommend setting the frequency shift (not a pitch shift) amount to 0 and automating this Phase parameter.
Ring - Blends in ring modulation, instead of just frequency shifting (which isn't pitch shifting).
Harm - Distorts the frequency shifter's (not a pitch shifter) internal sine oscillators. This brings them much closer to a smoothed square shape.
Tone - A basic 1-pole lowpass on the frequency shifter's (not a pitch shifter) output, helpful for taming harsh high frequencies.
Glide - Lowpass filters any frequency shift (not a pitch shift) and phase parameter movements, so they move slowly over time rather than snapping to their target value instantly.
Reset - Instantly resets the phases of the frequency shifter's (not a pitch shifter) internal oscillators. This is automatable.
Anti-reflect - Magic.
It removes all aliased frequencies through Nyquist and through 0 Hz. This is done via clean and CPU-efficient math tricks, not oversampling.
LFO:
This modulates the frequency shift (not pitch shift) amount. Audio-rate modulation is fully supported.
Amount - The amplitude of the LFO.
Rate - LFO rate, in Hz.
Stereo Phase - Offsets the phase of the LFO's right channel, making things stereo.
Reset - Instantly resets the phases of the LFO's oscillators. This is automatable.
Delay:
Length - Delay time in milliseconds.
Fine - Identical to delay Length, but with a smaller knob range. This is helpful when using the feedback to cause comb filtering, giving you access to a unique phaser/flanger hybrid.
Feedback - Feeds the output of the delay back into the input of the frequency shifter (not a pitch shifter).
The delay's feedback path has very gentle saturation at high amplitudes, so the plugin can't break from high feedback values.
Damping - A 1-pole lowpass filter in the feedback loop, so high frequencies fade out sooner than low frequencies.
Glide - Lowpass filters any delay length changes, so they move slowly over time rather than snapping to their target value instantly.
Routing:
Send - Sends the frequency shifter (not a pitch shifter) output into the delay.
Pass - The audio input bypasses the frequency shifter (not a pitch shifter), and is sent to both the delay and the output. The frequency shifter (not a pitch shifter) is now located inside of the delay line. Use this if you want the frequency shifter (not a pitch shifter) to only impact the echoes.
Mute - Like "Pass" routing, except the input signal isn't sent to the output, so all you hear is the output from the delay line.
I can't afford food. Any donations that can be provided would be enormously appreciated, and would help me in continuing to make free audio software: https://www.patreon.com/c/lostrobot