-
-
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
EngineEffectsDelay: effects chain delay handling #4810
Merged
daschuer
merged 40 commits into
mixxxdj:main
from
davidchocholaty:fix_pitch_shift_latency
Aug 14, 2022
Merged
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
461f6f7
EngineEffectsDelay: effects chain delay handling
davidchocholaty ad76619
EngineEffectsDelay: frames instead of samples
davidchocholaty f40e796
EngineEffectsDelay: add negative delay handling
davidchocholaty 93d2d19
EngineEffectsDelayTest: add delay handling tests
davidchocholaty 97fdb39
EngineEffectsDelayTest: add stereo assertion
davidchocholaty a1456a6
EffectProcessor: fix audio signals documentation
davidchocholaty cc5abdd
PitchShiftEffect: move getter into the header
davidchocholaty e958a08
EngineEffectsDelay: convert ramping to float
davidchocholaty 5d2d560
EngineEffectChain: avoid heap allocation
davidchocholaty 4f7515b
EngineEffectsDelay: ramping for the whole buffer
davidchocholaty 755c714
EngineEffectsDelay: inheritance from EngineObject
davidchocholaty 164e200
EngineEffectsDelay: refactor variable names
davidchocholaty bc78344
EngineEffectsDelay: remove code duplication
davidchocholaty 5f1ea36
EngineEffectsDelay: add benchmarks for testing
davidchocholaty 1b88485
EngineEffectsDelay: remove M_RESTRICT macro
davidchocholaty af33185
EngineEffectsDelay: replace const with constexpr
davidchocholaty 5892a7e
EngineEffectsDelay: remove unnecessary casts
davidchocholaty ea7d77c
Merge remote-tracking branch 'upstream/main' into fix_pitch_shift_lat…
davidchocholaty 047c3e2
EngineEffectsDelayTest: avoid manual allocation
davidchocholaty 1db08fd
EngineEffectsDelay: improve const correctness
davidchocholaty e84b096
EngineEffectsDelay: modulo calculation doc
davidchocholaty 3da53da
refactor(util): add `std::span` getter over SampleBuffer
Swiftb0y b0ed62c
SpanUtil: add castToSizeType method
davidchocholaty ff2fefe
EngineEffectsDelayTest: add using of std::span
davidchocholaty 7107c87
EngineEffectsDelay: rename kiMaxDelay to kMaxDelay
davidchocholaty ef31bfb
EngineEffectsDelayTest: manual iterators handling
davidchocholaty 11a83a8
EngineEffectsDelayTest: use by-index for loop
davidchocholaty f40707c
SpanUtil: add checking for signed type
davidchocholaty 42487b2
SpanUtil: replace class with namespace
davidchocholaty 79e6681
SampleBuffer: remove an unnecessary line of code
davidchocholaty 1e2d0f2
EngineEffectsDelayTest: replace with ASSERT
davidchocholaty 8e19313
EngineEffectsDelay: remove magic constant
davidchocholaty f7a38c9
EngineEffectsDelay: clamp wrong delay values
davidchocholaty ad65691
EngineEffectsDelayTest: add value clamping comment
davidchocholaty 2e14db1
EngineEffectsDelay: add kMaxDelayFrames constant
davidchocholaty 7f00a1a
EngineEffectsDelay: store input for zero delay
davidchocholaty 8945f3d
spanutil: templated functions as constexpr
davidchocholaty 9ffdb11
SampleBuffer: fix const for span method
davidchocholaty 80b9209
PitchShiftEffect: remove delay reporting
davidchocholaty 5342307
spanutil: add unsigned type checking
davidchocholaty File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
#include "engine/effects/engineeffectsdelay.h" | ||
|
||
#include "util/rampingvalue.h" | ||
#include "util/sample.h" | ||
|
||
EngineEffectsDelay::EngineEffectsDelay() | ||
: m_currentDelaySamples(0), | ||
m_prevDelaySamples(0), | ||
m_delayBufferWritePos(0) { | ||
m_pDelayBuffer = SampleUtil::alloc(kDelayBufferSize); | ||
SampleUtil::clear(m_pDelayBuffer, kDelayBufferSize); | ||
} | ||
|
||
void EngineEffectsDelay::process(CSAMPLE* pInOut, | ||
const int iBufferSize) { | ||
if (m_prevDelaySamples == 0 && m_currentDelaySamples == 0) { | ||
for (int i = 0; i < iBufferSize; ++i) { | ||
// Put samples into delay buffer. | ||
m_pDelayBuffer[m_delayBufferWritePos] = pInOut[i]; | ||
m_delayBufferWritePos = (m_delayBufferWritePos + 1) % kDelayBufferSize; | ||
} | ||
|
||
return; | ||
} | ||
|
||
// The "+ kDelayBufferSize" addition ensures positive values for the modulo calculation. | ||
// From a mathematical point of view, this addition can be removed. Anyway, | ||
// from the cpp point of view, the modulo operator for negative values | ||
// (for example, x % y, where x is a negative value) produces negative results | ||
// (but in math the result value is positive). | ||
int delaySourcePos = | ||
(m_delayBufferWritePos + kDelayBufferSize - m_currentDelaySamples) % | ||
kDelayBufferSize; | ||
|
||
if (m_prevDelaySamples == m_currentDelaySamples) { | ||
for (int i = 0; i < iBufferSize; ++i) { | ||
// Put samples into delay buffer. | ||
m_pDelayBuffer[m_delayBufferWritePos] = pInOut[i]; | ||
m_delayBufferWritePos = (m_delayBufferWritePos + 1) % kDelayBufferSize; | ||
|
||
// Take a delayed sample from the delay buffer | ||
// and copy it to the destination buffer. | ||
pInOut[i] = m_pDelayBuffer[delaySourcePos]; | ||
delaySourcePos = (delaySourcePos + 1) % kDelayBufferSize; | ||
} | ||
|
||
} else { | ||
// The "+ kDelayBufferSize" addition ensures positive values for the modulo calculation. | ||
// From a mathematical point of view, this addition can be removed. Anyway, | ||
// from the cpp point of view, the modulo operator for negative values | ||
// (for example, x % y, where x is a negative value) produces negative results | ||
// (but in math the result value is positive). | ||
int oldDelaySourcePos = | ||
(m_delayBufferWritePos + kDelayBufferSize - m_prevDelaySamples) % | ||
kDelayBufferSize; | ||
|
||
const RampingValue<CSAMPLE_GAIN> delayChangeRamped(0.0f, 1.0f, iBufferSize); | ||
|
||
for (int i = 0; i < iBufferSize; ++i) { | ||
// Put samples into delay buffer. | ||
m_pDelayBuffer[m_delayBufferWritePos] = pInOut[i]; | ||
m_delayBufferWritePos = (m_delayBufferWritePos + 1) % kDelayBufferSize; | ||
|
||
// Take delayed samples from the delay buffer | ||
// and with the use of ramping (cross-fading), | ||
// calculate the result sample value | ||
// and put it into the dest buffer. | ||
CSAMPLE_GAIN crossMix = delayChangeRamped.getNth(i); | ||
|
||
pInOut[i] = m_pDelayBuffer[oldDelaySourcePos] * (1.0f - crossMix); | ||
pInOut[i] += m_pDelayBuffer[delaySourcePos] * crossMix; | ||
|
||
oldDelaySourcePos = (oldDelaySourcePos + 1) % kDelayBufferSize; | ||
delaySourcePos = (delaySourcePos + 1) % kDelayBufferSize; | ||
} | ||
|
||
m_prevDelaySamples = m_currentDelaySamples; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
#pragma once | ||
|
||
#include "engine/engine.h" | ||
#include "engine/engineobject.h" | ||
#include "util/assert.h" | ||
#include "util/sample.h" | ||
#include "util/types.h" | ||
|
||
namespace { | ||
static constexpr int kMaxDelayFrames = | ||
mixxx::audio::SampleRate::kValueMax - 1; | ||
static constexpr int kDelayBufferSize = | ||
mixxx::audio::SampleRate::kValueMax * mixxx::kEngineChannelCount; | ||
} // anonymous namespace | ||
|
||
/// The effect can produce the output signal with a specific delay caused | ||
/// by the inner effect processing. Based on that the signal on the input | ||
/// of the effect does not overlap with the signal on the effect output. | ||
/// For using the effect in the Dry/Wet or Dry+Wet mode the dry signal | ||
/// and wet signal do not overlap and the effect latency is recognizable. | ||
/// | ||
/// The EngineEffectsDelay offers a solution to this situation by delaying | ||
/// the input dry signal by a specific amount of delay. The signal delaying | ||
/// handles the class method EngineEffectsDelay::process. The method | ||
/// can be used for the one specific effect same as the effect chain if | ||
/// the set delay is the delay of the whole effect chain (sum of the delay | ||
/// of all effects in the effect chain). | ||
/// | ||
/// After delaying the non-delayed signal, both signals (delayed | ||
/// and non-delayed) can be mixed and used together. | ||
class EngineEffectsDelay final : public EngineObject { | ||
public: | ||
EngineEffectsDelay(); | ||
|
||
virtual ~EngineEffectsDelay(){}; | ||
|
||
/// Called from the audio thread | ||
|
||
/// The method sets the number of frames of which will | ||
/// the EngineEffectsDelay::process method delays the input signal. | ||
/// By default, the EngineEffectsDelay::process method works with | ||
/// a zero delay. When is the delay set, the EngineEffectsDelay::process | ||
/// method works with this set delay value until the value is changed. | ||
void setDelayFrames(SINT delayFrames) { | ||
VERIFY_OR_DEBUG_ASSERT(delayFrames >= 0) { | ||
delayFrames = 0; | ||
} | ||
VERIFY_OR_DEBUG_ASSERT(delayFrames <= kMaxDelayFrames) { | ||
delayFrames = kMaxDelayFrames; | ||
} | ||
|
||
// Delay is reported from the effect chain by a number of frames | ||
// to aware problems with a number of channels. The inner | ||
// EngineEffectsDelay structure works with delay samples, so the value | ||
// is recalculated for the EngineEffectsDelay usage. | ||
m_currentDelaySamples = delayFrames * mixxx::kEngineChannelCount; | ||
} | ||
|
||
/// The method delays the input buffer by the set number of samples | ||
/// and returns the result in the output buffer. The input buffer | ||
/// is not changed. For zero delay the input buffer is copied into | ||
/// the output buffer. For non-zero delay, the output signal is returned | ||
/// with the delay. | ||
/// | ||
/// If the number of delay samples hasn't changed, between two | ||
/// EngineEffectsDelay::process method calls, the delayed signal buffers | ||
/// directly follow each other as an input buffer, however, with a delay. | ||
/// | ||
/// If the number of delayed samples has changed between two | ||
/// EngineEffectsDelay::process method calls, the new delay value is set | ||
/// as actual and the output buffer is filled using cross-fading | ||
/// of the presumed output buffer for the previous delay value | ||
/// and of the output buffer created using the new delay value. | ||
void process(CSAMPLE* pInOut, const int iBufferSize) override; | ||
|
||
private: | ||
SINT m_currentDelaySamples; | ||
SINT m_prevDelaySamples; | ||
SINT m_delayBufferWritePos; | ||
CSAMPLE* m_pDelayBuffer; | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 feel lucky you may fiddle around to either use factorized functions or make this loop vetcorized.
This can be checked like described here:
mixxx/src/util/sample.cpp
Line 14 in 0ad01b6
This requires that you chop the loop in chunks to get around the % kiMaxDelay on every sample.
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 not sure how much vectorizing potential there actually is. I think the
n % kiMaxDelay
always breaks the autovectorizer because you can't use vectorizing instructions whenn
is at the wrap-around boundary.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 missed that in my last note in this thread (which is why that note essentially repeated what you just said). The problem I see is that we simply can't do that. At least the current interface and implementation allows the delay to be any frame frame number. I don't think we can make the interface more granular so we'd have to find a workaround in the implementation.