add ping-pong loop mode and crossfade length rotary

This commit is contained in:
Armin 2026-07-24 13:09:52 +02:00
commit e9dc3a4d66
10 changed files with 67 additions and 17 deletions

View file

@ -91,11 +91,17 @@ void CustomSamplerVoice::startNote(int midiNoteNumber, float velocity, juce::Syn
playUntilEnd = sampleSound.playUntilEnd->get();
isLooping = sampleSound.isLooping->get() || wavetableMode;
loopingHasStart = isLooping && sampleSound.loopingHasStart->get() && sampleSound.loopStart < sampleSound.sampleStart && !wavetableMode;
isPingPong = isLooping && sampleSound.isPingPong->get() && !wavetableMode;
loopingHasStart = isLooping && sampleSound.loopingHasStart->get() && sampleSound.loopStart < sampleSound.sampleStart && !wavetableMode && !isPingPong;
loopStart = sampleSound.loopStart;
loopingHasEnd = isLooping && sampleSound.loopingHasEnd->get() && sampleSound.loopEnd > sampleSound.sampleEnd && !wavetableMode;
loopingHasEnd = isLooping && sampleSound.loopingHasEnd->get() && sampleSound.loopEnd > sampleSound.sampleEnd && !wavetableMode && !isPingPong;
loopEnd = sampleSound.loopEnd;
if (isPingPong)
playbackMode = PluginParameters::BASIC; // BungeeStretcher is forward-only
pingPongDirection = 1.0f;
effectiveStart = loopingHasStart ? loopStart : sampleStart;
effectiveEnd = loopingHasEnd ? loopEnd : sampleEnd;
@ -264,6 +270,9 @@ void CustomSamplerVoice::renderNextBlock(juce::AudioBuffer<float>& outputBuffer,
updateSpeedAndPitch(getCurrentlyPlayingNote(), pitchWheel);
if (isPingPong && isLooping)
speed *= pingPongDirection;
bool someFXEnabled{ false };
for (const auto& effect : effects)
{
@ -350,7 +359,17 @@ void CustomSamplerVoice::renderNextBlock(juce::AudioBuffer<float>& outputBuffer,
con.speedMovedSinceRelease += 1;
// Handle transitions
if (con.state == PLAYING && isLooping && con.currentPosition >= sampleEnd - crossfade) // Loop crossfade
if (con.state == PLAYING && isLooping && isPingPong && pingPongDirection > 0 && con.currentPosition >= sampleEnd) // Ping-pong: reverse at end
{
pingPongDirection = -1.0f;
speed = std::abs(speed) * -1.0f;
}
else if (con.state == PLAYING && isLooping && isPingPong && pingPongDirection < 0 && con.currentPosition <= sampleStart) // Ping-pong: reverse at start
{
pingPongDirection = 1.0f;
speed = std::abs(speed);
}
else if (con.state == PLAYING && isLooping && !isPingPong && con.currentPosition >= sampleEnd - crossfade) // Loop crossfade
{
con.currentPosition -= (sampleEnd - sampleStart + 1) - crossfade;
con.isCrossfadingLoop = true;

View file

@ -219,8 +219,9 @@ private:
float noteVelocity{ 0.f };
bool playUntilEnd{ false };
bool isLooping{ false }, loopingHasStart{ false }, loopingHasEnd{ false };
bool isLooping{ false }, loopingHasStart{ false }, loopingHasEnd{ false }, isPingPong{ false };
int sampleStart{ 0 }, sampleEnd{ 0 }, loopStart{ 0 }, loopEnd{ 0 };
float pingPongDirection{ 1.0f };
/** We call this "smoothing" but it's a pretty normal attack/release envelope. */
float attackSmoothing{ 0.f }, releaseSmoothing{ 0.f };

View file

@ -37,6 +37,7 @@ SamplerParameters::SamplerParameters(const juce::AudioProcessorValueTreeState& a
isLooping(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::IS_LOOPING))),
loopingHasStart(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::LOOPING_HAS_START))),
loopingHasEnd(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::LOOPING_HAS_END))),
isPingPong(dynamic_cast<juce::AudioParameterBool*>(apvts.getParameter(PluginParameters::IS_PING_PONG))),
sampleStart(pluginState.sampleStart), sampleEnd(pluginState.sampleEnd),
loopStart(pluginState.loopStart), loopEnd(pluginState.loopEnd),

View file

@ -34,7 +34,7 @@ public:
/** Playback details */
juce::AudioParameterFloat* gain, * speedFactor, * octaveSpeedFactor, * attack, * release, * attackShape, * releaseShape, * a4_freq, * pitchWheelRange, * wideTuningControl;
juce::AudioParameterInt* semitoneTuning, * centTuning, * waveformSemitoneTuning, * waveformCentTuning, * crossfadeSamples;
juce::AudioParameterBool* monoOutput, * disableVelocity, * skipAntialiasing, * applyFXPre, * playUntilEnd, * disableWavetableMode, * isLooping, * loopingHasStart, * loopingHasEnd;
juce::AudioParameterBool* monoOutput, * disableVelocity, * skipAntialiasing, * applyFXPre, * playUntilEnd, * disableWavetableMode, * isLooping, * loopingHasStart, * loopingHasEnd, * isPingPong;
ListenableAtomic<int>& sampleStart, & sampleEnd, & loopStart, & loopEnd;
juce::AudioParameterInt* midiStart, * midiEnd, * midiRoot;