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

PitchShiftEffect: add independent effect #4775

Merged
merged 4 commits into from
Jun 10, 2022

Conversation

davidchocholaty
Copy link
Contributor

Adds a new independent pitch shift effect.
The pitch shift effect contains for now one knob for setting up
the pitch of a sound. The knob has a fixed range for one octave
up or down and works in linear continuous mode.

The known issue is, that the RubberBand library processing latency
needs to be taken into account for different pitch values.
Based on that the effect works with an amount of latency.

Implements: lp:1299035 "Add a Transpose / Pitch shift effect"
Reported by: RJ Skerry-Ryan

This commit adds a new independent pitch shift effect.
The pitch shift effect contains one knob for setting up
the pitch of a sound. The knob has a fixed range for one octave
up or down. The knob works in linear continuous mode.

The known issue is, that RubberBand library processing latency
needs to be taken into account for different pitch values.
Based on that the effect works with an amount of latency.
@github-actions github-actions bot added the build label May 30, 2022
Copy link
Member

@Swiftb0y Swiftb0y left a comment

Choose a reason for hiding this comment

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

Thank you. Code looks very good already.

src/effects/backends/builtin/pitchshifteffect.cpp Outdated Show resolved Hide resolved
src/effects/backends/builtin/pitchshifteffect.cpp Outdated Show resolved Hide resolved
src/effects/backends/builtin/pitchshifteffect.h Outdated Show resolved Hide resolved
src/effects/backends/builtin/pitchshifteffect.h Outdated Show resolved Hide resolved
src/effects/backends/builtin/pitchshifteffect.h Outdated Show resolved Hide resolved
src/effects/backends/builtin/pitchshifteffect.cpp Outdated Show resolved Hide resolved
@Swiftb0y
Copy link
Member

Your header seems to mismatch the implementation

 Error: /home/runner/work/mixxx/mixxx/src/effects/backends/builtin/pitchshifteffect.cpp:5:23: error: function previously declared with an implicit exception specification redeclared with an explicit exception specification [-Werror,-Wimplicit-exception-spec-mismatch]
PitchShiftGroupState::~PitchShiftGroupState() noexcept {
                      ^
/home/runner/work/mixxx/mixxx/src/effects/backends/builtin/pitchshifteffect.h:29:13: note: previous declaration is here
    virtual ~PitchShiftGroupState();

@Swiftb0y
Copy link
Member

After you have addressed my review comments, I suggest you work on getting the usual Rubberband kinks worked out (latency compensation) primarily.

@davidchocholaty
Copy link
Contributor Author

Thank you for the tips and improvement proposals. I agree with mentioned fixes and then I will focus on solving the latency issue.

@Swiftb0y
Copy link
Member

Be warned: I don't know how much we can do about the latency without large changes to the effects system. Please discuss your findings and challenges early.

SampleUtil::free(m_retrieveBuffer[0]);
SampleUtil::free(m_retrieveBuffer[1]);

VERIFY_OR_DEBUG_ASSERT(m_pRubberBand) {
Copy link
Contributor

Choose a reason for hiding this comment

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

All this code is probably redundant.

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 couldn't find the other part of the code, where it should be done. This code was based on how other built-in effects use SampleUtil::free in their destructors. For example, see:

LinkwitzRiley8EQEffectGroupState::~LinkwitzRiley8EQEffectGroupState() {

or

~LVMixEQEffectGroupState() override {

Copy link
Member

Choose a reason for hiding this comment

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

This can be a normal debug assert.


// static
QString PitchShiftEffect::getId() {
return "org.mixxx.effects.pitchshift";
Copy link
Contributor

Choose a reason for hiding this comment

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

This could become a constant.

Copy link
Member

Choose a reason for hiding this comment

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

And also a QLatin1String

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the result is expected to be a QString so using QLatin1String would not make sense here.

Copy link
Member

Choose a reason for hiding this comment

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

Mhmm I figured it was faster but looking at the Qt implemenation, you are right its not. We should change that in the future.

src/effects/backends/builtin/pitchshifteffect.h Outdated Show resolved Hide resolved

// static
QString PitchShiftEffect::getId() {
return "org.mixxx.effects.pitchshift";
Copy link
Member

@daschuer daschuer May 31, 2022

Choose a reason for hiding this comment

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

I think you mean a QStringLiteral

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 used QString because all other built-in effects use it too.

pitch->setName(QObject::tr("Pitch"));
pitch->setShortName(QObject::tr("Pitch"));
pitch->setDescription(QObject::tr(
"The new pitch of a sound."));
Copy link
Member

Choose a reason for hiding this comment

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

I think it is "The pitch shift applied to the sound"

Copy link
Member

Choose a reason for hiding this comment

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

IMO we can tweak the exact wording later. Discussing this is bikeshedding.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's true, that I planned to improve effect descriptions a little bit later. Anyway, the proposal by @daschuer sounds better, than my version. I will use it. Thank you.

pitch->setDescription(QObject::tr(
"The new pitch of a sound."));
pitch->setValueScaler(EffectManifestParameter::ValueScaler::Linear);
pitch->setUnitsHint(EffectManifestParameter::UnitsHint::Unknown);
Copy link
Member

Choose a reason for hiding this comment

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

You may also consider to extend unitsHint. I am afraid it is currently unused but we may consider to inform the user about the units.

Copy link
Member

Choose a reason for hiding this comment

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

I agree, a percentage or a ratio UnitsHint would make sense, but we can do that later.

src/effects/backends/builtin/pitchshifteffect.cpp Outdated Show resolved Hide resolved
framesAvailable,
engineParameters.framesPerBuffer());
SINT receivedFrames = pState->m_pRubberBand->retrieve(
(float* const*)pState->m_retrieveBuffer,
Copy link
Member

Choose a reason for hiding this comment

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

Remove the cast here as well.

src/effects/backends/builtin/pitchshifteffect.h Outdated Show resolved Hide resolved
@daschuer
Copy link
Member

I have just played with it and "Nathan Evans - Wellerman" sounds great with the high pitched voice and the (unwanted) echo and the dry/wet in center position :-)

This echo needs to be compensated. Maybe you can actually expose this compensation delay as additional parameter for making funny things without extra CPU cycles.

The pitch knob is crackling at the center position which is a known issue. In case of the center position this is a known issue. I have tried to fix it here without success: #4030
Unfortunately I hear also crackling in other positions, not sure if we have solved that somehow in the mixer implementation.
You can best observe it with a 440 Hz sine wave than van be generated by Audacity.

Actually the crossfade soluton is a solution at the wrong end. It would be much better to have this solved inside the library.
Maybe we should discuss this with the upstream developers. (Of cause, it also depends to in which direction your project is heading)

@daschuer
Copy link
Member

Another issue is that your new effect is not visible by default, like all other internal effects.

@Swiftb0y
Copy link
Member

Another issue is that your new effect is not visible by default,

I think thats fine until this effect is finished. The problem is that I don't know how we can make it visible by default once we want that.

This echo needs to be compensated.

I agree, I pointed that out earlier, but I don't know how easy that is. Regardless, its still a major priority.

@daschuer
Copy link
Member

daschuer commented Jun 1, 2022

We have a latency compensation in our bessel filter. It uses

class EngineFilterDelay : public EngineObjectConstIn {

The calculation of the delay is done here. The "issue" is that the delay buffer works only with full sample size delays:

const double delayRatioTable[] = {

@davidchocholaty
Copy link
Contributor Author

I have just played with it and "Nathan Evans - Wellerman" sounds great with the high pitched voice and the (unwanted) echo and the dry/wet in center position :-)

This echo needs to be compensated. Maybe you can actually expose this compensation delay as additional parameter for making funny things without extra CPU cycles.

The pitch knob is crackling at the center position which is a known issue. In case of the center position this is a known issue. I have tried to fix it here without success: #4030 Unfortunately I hear also crackling in other positions, not sure if we have solved that somehow in the mixer implementation. You can best observe it with a 440 Hz sine wave than van be generated by Audacity.

Actually the crossfade soluton is a solution at the wrong end. It would be much better to have this solved inside the library. Maybe we should discuss this with the upstream developers. (Of cause, it also depends to in which direction your project is heading)

Thank you. That's a cool idea with a compensation delay. Maybe I can start a new topic on Zulip for this issue. Do you agree?

@davidchocholaty
Copy link
Contributor Author

Another issue is that your new effect is not visible by default, like all other internal effects.

My idea was the same as @Swiftb0y mentioned, to make it visible after the effect will be done. Anyway, I could do it now. I am not sure too if I know, how to make the effect visible by default.

@davidchocholaty
Copy link
Contributor Author

We have a latency compensation in our bessel filter. It uses

class EngineFilterDelay : public EngineObjectConstIn {

The calculation of the delay is done here. The "issue" is that the delay buffer works only with full sample size delays:

const double delayRatioTable[] = {

Thank you for the tips. I just let to stir up the conversation for the first pull request commit. I was fixing the code continuously, so I just will make the last changes for the following fixing commit and I will focus on the latency issue.

@daschuer
Copy link
Member

daschuer commented Jun 1, 2022

My idea was the same as @Swiftb0y mentioned, to make it visible after the effect will be done.

That's reasonable.
However I am afraid we have no feature to handle new effects. After a fresh installation (not verified) all internal effects will be shown, from the next start this is used as user settings, so new effects are not shown, since they are not listed in theses settings. This is probably a topic of another PR.

Since this effect is already fun, it we can merge is any time when you think this PR is finished. And we can discuss the delay issue at Zulip.

This commit fixes destructor exception specification error
and refactors code. The code changes are based on the discussion
for the previous PitchShiftEffect commit. Major changes in this commit
are: fix destructor explicit exception specification error, complex
methods movement from the header file into the .cpp file
for PitchShiftGroupState class, use lambda expression for the pitch
value assignment, remove unnecessary casts, add the QStringLiteral
to the getId method and the PitchShiftEffect the class was set
as final.
This commit changes m_pRubberBand stereo buffer size to the count
of the frames per buffer for current engine parameters.
Copy link
Member

@Swiftb0y Swiftb0y left a comment

Choose a reason for hiding this comment

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

LGTM, anyone else willing to take another look before merging?

return;
}

m_pRubberBand->reset();
Copy link
Member

Choose a reason for hiding this comment

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

This is redundant, because it will be called anyway invisibility. No need to care abeut the whole thing of an unique_ptr.

Copy link
Member

Choose a reason for hiding this comment

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

Can you elaborate? This calls RubberBandStretcher::reset() not, unique_ptr::reset()... RubberBandStretcher::reset() will not be called implicitly, it still might be redundant though since we're in the dtor anyways.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, yes I have actually misread the code as m_pRubberBand.reset() sorry.

But you assumption that it is redundant anyway is also correct. It all ends here
https://github.com/breakfastquay/rubberband/blob/bad529f81e8ae66bd4535a6af392efa38c7fc6b1/src/StretcherImpl.cpp#L222 with almost the same code in the destructor.

SampleUtil::free(m_retrieveBuffer[0]);
SampleUtil::free(m_retrieveBuffer[1]);

VERIFY_OR_DEBUG_ASSERT(m_pRubberBand) {
Copy link
Member

Choose a reason for hiding this comment

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

This can be a normal debug assert.

@davidchocholaty
Copy link
Contributor Author

This can be a normal debug assert.

Okay. Anyway, if I remove the duplicated reset of the unique_ptr, IMO it isn't necessary to check it at all.

The commit removes duplicated RubberBandStretcher::reset() call
from the PitchShiftGroupState destructor. The functionality
of the reset method is done by RubberBandStretcher on its own.
@daschuer
Copy link
Member

daschuer commented Jun 3, 2022

Thank you! @uklotzde Are your findings also solved?

@Swiftb0y
Copy link
Member

Swiftb0y commented Jun 9, 2022

I think we've waited long enough for @uklotzde. Since this is fast-moving GSoC Project and all of the review comments have been addressed, I propose to merge this as-is so we don't hold up any progress. Do you agree @daschuer?

@uklotzde
Copy link
Contributor

uklotzde commented Jun 9, 2022

I think we've waited long enough for @uklotzde. Since this is fast-moving GSoC Project and all of the review comments have been addressed, I propose to merge this as-is so we don't hold up any progress. Do you agree @daschuer?

The title of the PR says WIP? Is this considered a draft or not?

@Swiftb0y
Copy link
Member

Swiftb0y commented Jun 9, 2022

I think we've waited long enough for @uklotzde. Since this is fast-moving GSoC Project and all of the review comments have been addressed, I propose to merge this as-is so we don't hold up any progress. Do you agree @daschuer?

The title of the PR says WIP? Is this considered a draft or not?

ping @davidchocholaty
Is this intentional or did you forget about removing that? In the future, just open a draft PR

@davidchocholaty
Copy link
Contributor Author

Oh, I'm sorry. At the time I created the PR I didn't expect, that the PR will be merged until all effect parts and features will be done. I will remove the [WIP] tag.

@davidchocholaty davidchocholaty changed the title [WIP] PitchShiftEffect: add independent effect PitchShiftEffect: add independent effect Jun 10, 2022
@daschuer
Copy link
Member

Thank you. That's a nice first step.

What will you do next?

My approach with the Bessel EQ was this:

I have created a 440 Hz sine wave using Audacity, with gaps of silence. Than play it with half dry wet and record it via Mixxx. Than load the recording into Audacy. You should clearly see the latency. I Have noted the latency into a spread sheet. Repeat this with different settings to get a feeling what is the issue we deal with.
Than I but the data into a diagram and try to find experimental a curve that was created by a math formular with similar values.

You may also confirm that with different frequencies. Alternatively you may use a logarithmic sweep of 20 Hz to 22 kHz with gaps to see the whole spectrum in a single file.

The optimum solution would be to report the latency to the comon dry/wet Mixer.

An intermediate solution would be to put an extra dry/wet knob into the effect.

Not sure if this approach fits here as well. What do you think?

@daschuer daschuer merged commit da71f8d into mixxxdj:main Jun 10, 2022
@davidchocholaty
Copy link
Contributor Author

Thank you so much for merging my first PR for Mixxx. It's really motivating for me. Your research for the Bessel EQ really impressed me. Thank you for all the tips. I will beggin with the reseach for the Pitch shift effect latency in the same way. It seems like a good idea to sweep in range of 20Hz to 22 kHz. I will inform you and other developers about the results of the research on the Zulip chat. I wouldn't be afraid of the better optimal solution according to your proposals. IMO the effect fits better into the effect rack and will be more intuitive for the user.

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

Successfully merging this pull request may close these issues.

4 participants