-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Allow effect to continue processing if its buffer isn't empty (builtin reverb/delay) #14099
base: main
Are you sure you want to change the base?
Conversation
Happy New Year and Welcome at Mixxx! |
Hi @JoergAtGithub , happy new year as well to you and thanks for answering! I have just signed the |
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.
Thanks for your feature. Lets iterate on this a bit.
// Function to determine if reverb tail is gone | ||
// Calculate absolute difference between wet and dry buffers for the tail | ||
float differenceSum = 0.0f; | ||
const SINT tailCheckLength = engineParameters.samplesPerBuffer() / 4; | ||
for (SINT i = engineParameters.samplesPerBuffer() - tailCheckLength; | ||
i < engineParameters.samplesPerBuffer(); | ||
++i) { | ||
differenceSum += fabsf(pOutput[i] - pInput[i]); | ||
} | ||
// Calculate average of the differences | ||
const float averageDifference = differenceSum / tailCheckLength; | ||
constexpr float threshold = 0.002f; | ||
const bool reverbTailFullyFaded = averageDifference < threshold; | ||
if (reverbTailFullyFaded) { | ||
m_isReadyForDisable = true; | ||
} |
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.
Then lets make it a function
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 made it into a function :--)
// Function to determine if echo tail is gone | ||
// Calculate absolute if the delayline-buffer is approx. zero/empty. | ||
pGroupState->prev_send = 0; | ||
} else { | ||
pGroupState->prev_send = send_current; | ||
float differenceSum = 0.0f; | ||
for (SINT i = 0; i < pGroupState->delay_buf.size(); | ||
i = i + 16) { // We don't need to consider *all* samples, | ||
// assuming 44.1kHz sample-rate this sums every | ||
// ~1/3ms | ||
differenceSum += fabsf(pGroupState->delay_buf[i]); | ||
} | ||
// Calculate average of the differences | ||
constexpr float threshold = 0.00001f; | ||
const bool echoTailFullyFaded = differenceSum < threshold; | ||
if (echoTailFullyFaded) { | ||
m_isReadyForDisable = true; | ||
|
||
pGroupState->delay_buf.clear(); | ||
} |
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.
same here. lets just actually make a function.
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 made it into a function :--)
@@ -110,6 +110,9 @@ class EffectProcessor { | |||
/// the dry signal is delayed to overlap with the output wet signal | |||
/// after processing all effects in the effects chain. | |||
virtual SINT getGroupDelayFrames() = 0; | |||
virtual bool isReadyForDisable(void) { |
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.
Style NIT:
virtual bool isReadyForDisable(void) { | |
virtual bool isReadyForDisable() { |
float averageSampleDifferenceEnergy(const SINT samplesPerBuffer, | ||
const CSAMPLE* buffer_in, | ||
const CSAMPLE* buffer_out, | ||
const SINT tailCheckLength); |
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.
If you don't need to call it from outside the class, don't declare it in the header. Move it into the anonymous namespace in the .cpp
instead.
float averageSampleDifferenceEnergy(const SINT samplesPerBuffer, | |
const CSAMPLE* buffer_in, | |
const CSAMPLE* buffer_out, | |
const SINT tailCheckLength); |
for (SINT i = samplesPerBuffer - tailCheckLength; | ||
i < samplesPerBuffer; | ||
++i) { | ||
differenceSum += fabsf(buffer_out[i] - buffer_in[i]); |
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.
differenceSum += fabsf(buffer_out[i] - buffer_in[i]); | |
differenceSum += std::abs(buffer_out[i] - buffer_in[i]); |
const SINT tailCheckLength) { | ||
float differenceSum = 0.0f; |
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.
avoid div-by-zero.
const SINT tailCheckLength) { | |
float differenceSum = 0.0f; | |
const SINT tailCheckLength) { | |
if (tailCheckLength == 0) { | |
return 0; | |
} | |
float differenceSum = 0.0f; |
@@ -118,6 +118,17 @@ void EchoEffect::loadEngineEffectParameters( | |||
m_pTripletParameter = parameters.value("triplet"); | |||
} | |||
|
|||
float averageSampleEnergy(SINT delayBufferSize, mixxx::SampleBuffer const& delay_buffer) { |
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.
consider taking a std::span<const CSAMPLE>
instead. You can easily obtain one from the a mixxx::SampleBuffer
using its span()
member function.
// ~1/3ms | ||
differenceSum += fabsf(delay_buffer[i]); | ||
} | ||
return differenceSum; |
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.
This is not an average, so the function name is slightly confusing IMO. Lets just return the average instead or change the name.
for (SINT i = 0; i < delayBufferSize; | ||
i = i + 16) { // We don't need to consider *all* samples, |
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.
Are you sure? This just seems like downsampling that would introduce artifacts and miss high frequency signals. Is this just an optimization?
@Swiftb0y thanks for the thorough review, definitely some valid points. January is a bit hectic for me, I will address them and ask for re-review when I have some quiet time :--) |
Summary
Hey guys, this imperfection of mixxx's default effects has frustrated me personally when using mixxx in the past, and now I have set out to potentially fix it ;) . This functionality feels much better than the current "hard-cutoff" for me. It enhances the perception of turning on/off reverb or delay effects (that have a buffer) by playing the effect until the buffer is empty. This is how the reverb/echo effects of e.g. Pioneer Digital DJ-Mixers behave.
Works smoothly in my local builds, all tests are passing for this code. I would be very happy about a review and comments on what would be needed to potentially get this in :--)
Cheers, all the best to you.
Related Issues
Notable Code changes
EffectEnableState::Disabling
state would previously be used only for one buffer-processing iteration, as far as I can see mainly to ramp volume. With this new code, theEffectEnableState::Disabling
can be prolonged by effects responding to the call toisReadyForDisable()
withfalse
, and the effect can continue playing sound.EffectProcessor
base-class gets the new functionvirtual bool isReadyForDisable(void)
that defaults to true (current behaviour, hard cutoff of sound when effect is switched off). By overwriting this function effects can control weather they still have sound to play, even after the "shutoff" button has been pressed, or not.When will effects then be disabled?
EffectEnableState::Disabling
. I find this not ideal, although one seldomly sets feedback==1 in practice I guess. Any ideas?Ideas