-
-
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.
@@ -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.
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.
done but I feel a bit insecure about this, please have a look if this is as you intended ;)
@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 :--) |
Thanks for looking into this feature @mrnicegyu11! I am definitely interested in getting this in 2.6 beta, planned for the end of the month. Do you need some help to complete this PR? |
@acolombier Thanks for your kind words, I will try to address the comments in the PR this weekend. If I dont manage till then please feel free to jump in. Just as a disclaimer: I've worked with |
Looking again I agree with the reviewer fully though that I have some ambiguous naming and some premature optimizations in there, probably best to clean that up once more |
Co-authored-by: Swiftb0y <[email protected]>
Co-authored-by: Swiftb0y <[email protected]>
Co-authored-by: Swiftb0y <[email protected]>
…gy() from outside the class, don't declare it in the header
…ate averageSampleEnergy over whole delay ringbuffer, actually return avergae
Alright, change requests are addressed 🦾 hope this is ok, let me know, thanks for the very friendly communication to you guys, very welcoming :--) |
CC @acolombier ;) |
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.
couple more nits
// Calculate if the delayline-buffer is approx. zero/empty. | ||
const float avgSampleEnergy = averageSampleEnergy(pGroupState->delay_buf); | ||
// If echo tail fully faded | ||
if (avgSampleEnergy < (0.00001f / delayBufferSize)) { |
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.
why does the energy depend on the buffer size? The average shouldn't be influenced by the sample size of the dataset.
If the concern is that the fading tail influences the current average, simply don't sample it. Only sample as much as you need:
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.
The average sample energy depends on the buffer-size since it is computed from the buffer (average over buffer length, the average over the next buffer as well, or only half the buffer, would be numerically different). Thus, avgSampleEnergy
implicitly depends on the buffer length.
Before your previous review, the averageSampleEnergy
would not return an average but the summed Sample Energy over the buffer, this was simply bad code and is now fixed. To retain the exactly same threshold which was test-driven by me as before, the 0.00001f
magic number now needs to be divided by the delayBufferSize
. Of course I can also shift the 0.00001f
a bit by making it 100x smaller or so and call it a day :--) let me know
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.
Thus, avgSampleEnergy implicitly depends on the buffer length.
Well, now that we're only looking at the samples at the beginning of the delay buffer (exactly the samples that feedback into the effect to be exact) the size shouldn't anymore, right? Am I making sense here?
Besides I think .first(engineParameters.sampleRate())
is probably wrong, my mistake. I meant .first(<how big the current playback buffer is>)
...
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.
Mhm I am not sure I follow sorry, I think the average is calculated over the whole delay buffer. But we can in any case remove the division by the delay buffer length in the treshhold check. This is just numerics:--)
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 looks like the solution is not indepenedent of single clicks vs pink noise.
The noise value will contribute way more than a single click.
If we assume that the last added sample was above the threshold, we can alternatively just consume the number samples according to the current picked delay value. Not sure if the earlay disabling has benefits. Does it?
Did you consicder to loop through the whole buffer and continue once you have found a single semale above a certain thresshold?
Maybe copy/reuse the approch that we took here:
mixxx/src/analyzer/analyzersilence.cpp
Line 27 in ae2dc10
Iterator first_sound(Iterator begin, Iterator end) { |
Co-authored-by: Swiftb0y <[email protected]>
Co-authored-by: Swiftb0y <[email protected]>
@Swiftb0y please check again :--) |
@@ -118,6 +120,14 @@ void EchoEffect::loadEngineEffectParameters( | |||
m_pTripletParameter = parameters.value("triplet"); | |||
} | |||
|
|||
float averageSampleEnergy(std::span<const CSAMPLE> 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.
Energy is voltage * current. Here you use only voltage.
float averageSampleEnergy(std::span<const CSAMPLE> delay_buffer) { | |
float averageSampleLevel(std::span<const CSAMPLE> 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.
We collect here such sample functions that are reusable together with a hint that indicates if the function is auto vectorized.
This is probably already the case here, but worth to double check.
Line 463 in ae2dc10
CSAMPLE SampleUtil::maxAbsAmplitude(const CSAMPLE* pBuffer, SINT numSamples) { |
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.
Thank you for this nice PR and sorry for juming in late.
I have left some comments.
// Calculate if the delayline-buffer is approx. zero/empty. | ||
const float avgSampleEnergy = averageSampleEnergy(pGroupState->delay_buf); | ||
// If echo tail fully faded | ||
if (avgSampleEnergy < (0.00001f / delayBufferSize)) { |
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 looks like the solution is not indepenedent of single clicks vs pink noise.
The noise value will contribute way more than a single click.
If we assume that the last added sample was above the threshold, we can alternatively just consume the number samples according to the current picked delay value. Not sure if the earlay disabling has benefits. Does it?
Did you consicder to loop through the whole buffer and continue once you have found a single semale above a certain thresshold?
Maybe copy/reuse the approch that we took here:
mixxx/src/analyzer/analyzersilence.cpp
Line 27 in ae2dc10
Iterator first_sound(Iterator begin, Iterator end) { |
} else { | ||
pState->sendPrevious = sendCurrent; | ||
// Calculate absolute difference between wet and dry buffers for the tail | ||
const SINT tailCheckLength = engineParameters.samplesPerBuffer() / 4; |
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.
Why you /4 ?
It would be ideal if the feature works the same independent form samplesPerBuffer().
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.
We have here two delay filters per channel:
Line 216 in ae2dc10
tank.delay[0].init (L(6)); |
Initalized with 2^6 and 2^8 buffer size. This means the maximum delay is 256 frames.
The issue here in addition is that you are checking the current buffer and not was has added to the reverb.
EffectEnableState::Disabling means that no new samples are added, right?
Cant we just always process additional 256 Samples maybe a few more because of band filters, and than disable?
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