@@ -676,17 +676,15 @@ static void reseekStretcher (Voice& v, const SampleData& sample)
676676 seekLen = std::min (seekLen, v.endSample - v.startSample );
677677 seekLen = juce::jlimit (0 , (int ) v.stretchInBufL .size (), seekLen);
678678
679- int maxFrame = sample.getNumFrames () - 1 ;
680- if (seekLen > 0 && maxFrame >= 0 )
679+ if (seekLen > 0 && sample.getNumFrames () > 0 )
681680 {
682681 for (int i = 0 ; i < seekLen; ++i)
683682 {
684- int srcIdx = (v.direction > 0 )
685- ? (int ) v.stretchSrcPos + i
686- : (int ) v.stretchSrcPos - i;
687- srcIdx = juce::jlimit (0 , maxFrame, srcIdx);
688- v.stretchInBufL [(size_t ) i] = sample.getInterpolatedSample (srcIdx, 0 );
689- v.stretchInBufR [(size_t ) i] = sample.getInterpolatedSample (srcIdx, 1 );
683+ double srcPos = (v.direction > 0 )
684+ ? v.stretchSrcPos + i
685+ : v.stretchSrcPos - i;
686+ v.stretchInBufL [(size_t ) i] = readExactLoopSample (v, sample, srcPos, 0 );
687+ v.stretchInBufR [(size_t ) i] = readExactLoopSample (v, sample, srcPos, 1 );
690688 }
691689 float * ptrs[2 ] = { v.stretchInBufL .data (), v.stretchInBufR .data () };
692690 v.stretcher ->outputSeek (ptrs, seekLen);
@@ -871,28 +869,16 @@ static void fillBungeeBlock (Voice& v, const SampleData& sample)
871869 // Advance source position from the engine
872870 v.bungeeSrcPos = request.position ;
873871
874- // Canonicalize position back into loop range for next grain
872+ // Unbounded phase: do NOT canonicalize bungeeSrcPos back into loop range.
873+ // readExactLoopSample() maps grain read positions through the loop at read time.
874+ // This keeps Bungee's overlap-add state coherent across loop boundaries.
875875 if (v.pingPong )
876876 {
877- const double lo = (double ) v.startSample ;
878- const double hi = (double ) (v.endSample - 1 );
879- while (v.bungeeSrcPos > hi || v.bungeeSrcPos < lo)
880- {
881- if (v.bungeeSrcPos > hi)
882- {
883- v.bungeeSrcPos = 2.0 * hi - v.bungeeSrcPos ;
884- v.bungeeSpeed = -v.bungeeSpeed ;
885- }
886- if (v.bungeeSrcPos < lo)
887- {
888- v.bungeeSrcPos = 2.0 * lo - v.bungeeSrcPos ;
889- v.bungeeSpeed = -v.bungeeSpeed ;
890- }
891- }
892- }
893- else if (v.looping )
894- {
895- v.bungeeSrcPos = wrapLoopPosition (v.bungeeSrcPos , v.startSample , v.endSample );
877+ // Flip speed only when crossing a boundary in the current travel direction
878+ if (v.bungeeSpeed > 0.0 && v.bungeeSrcPos >= (double ) v.endSample )
879+ v.bungeeSpeed = -std::abs (v.bungeeSpeed );
880+ else if (v.bungeeSpeed < 0.0 && v.bungeeSrcPos < (double ) v.startSample )
881+ v.bungeeSpeed = std::abs (v.bungeeSpeed );
896882 }
897883}
898884
@@ -1019,7 +1005,13 @@ void VoicePool::processVoiceSample (int i, const SampleData& sample, double /*sr
10191005 v.bungeeOutReadPos ++;
10201006 }
10211007
1022- voicePositions[i].store ((float ) v.bungeeSrcPos , std::memory_order_relaxed);
1008+ // Wrap unbounded Bungee phase for UI cursor display only
1009+ if (v.looping )
1010+ voicePositions[i].store ((float ) wrapLoopPosition (v.bungeeSrcPos , v.startSample , v.endSample ), std::memory_order_relaxed);
1011+ else if (v.pingPong )
1012+ voicePositions[i].store ((float ) reflectPingPongPosition (v.bungeeSrcPos , v.startSample , v.endSample ), std::memory_order_relaxed);
1013+ else
1014+ voicePositions[i].store ((float ) v.bungeeSrcPos , std::memory_order_relaxed);
10231015 }
10241016 else
10251017 {
0 commit comments