@@ -510,7 +510,9 @@ void SamplerNode::handleNoteOn(MidiKeyboardState* source, int midiChannel, int m
510510 if (hm == HIT_RESET )
511511 {
512512 sn->jumpGhostSample = sn->playingSample ;
513- sn->jumpGhostGain = sn->adsr .getOutput ();
513+ sn->jumpGhostGain = sn->adsr .getOutput () * sn->velocity ;
514+ sn->jumpGhostFadeTotal = jmax (1 , (int )(AudioManager::getInstance ()->currentSampleRate * 0 .02f ));
515+ sn->jumpGhostFadeRemaining = sn->jumpGhostFadeTotal ;
514516 sn->adsr .reset ();
515517 sn->playingSample = 0 ;
516518 sn->rtPitchReadSample = 0 ;
@@ -521,7 +523,7 @@ void SamplerNode::handleNoteOn(MidiKeyboardState* source, int midiChannel, int m
521523 double shift = MidiMessage::getMidiNoteInHertz (midiNoteNumber) / MidiMessage::getMidiNoteInHertz (closestNote);
522524 samplerNotes[midiNoteNumber]->setAutoKey (samplerNotes[closestNote], shift);
523525
524- sn->jumpGhostGain = sn->adsr .getOutput ();
526+ sn->jumpGhostGain = sn->adsr .getOutput () * sn-> velocity ;
525527 sn->adsr .reset ();
526528 sn->velocity = velocity;
527529 sn->adsr .gate (1 );
@@ -548,7 +550,9 @@ void SamplerNode::handleNoteOn(MidiKeyboardState* source, int midiChannel, int m
548550 if (hm == HIT_RESET )
549551 {
550552 sn->jumpGhostSample = sn->playingSample ;
551- sn->jumpGhostGain = sn->adsr .getOutput ();
553+ sn->jumpGhostGain = sn->adsr .getOutput () * sn->velocity ;
554+ sn->jumpGhostFadeTotal = jmax (1 , (int )(AudioManager::getInstance ()->currentSampleRate * 0 .02f ));
555+ sn->jumpGhostFadeRemaining = sn->jumpGhostFadeTotal ;
552556 sn->adsr .reset ();
553557 sn->playingSample = 0 ;
554558 }
@@ -560,7 +564,9 @@ void SamplerNode::handleNoteOn(MidiKeyboardState* source, int midiChannel, int m
560564 if (sn->adsr .getState () != CurvedADSR::env_idle)
561565 {
562566 sn->jumpGhostSample = sn->playingSample ;
563- sn->jumpGhostGain = sn->adsr .getOutput ();
567+ sn->jumpGhostGain = sn->adsr .getOutput () * sn->velocity ;
568+ sn->jumpGhostFadeTotal = jmax (1 , (int )(AudioManager::getInstance ()->currentSampleRate * 0 .02f ));
569+ sn->jumpGhostFadeRemaining = sn->jumpGhostFadeTotal ;
564570 }
565571 sn->adsr .reset ();
566572 sn->playingSample = 0 ;
@@ -868,20 +874,13 @@ void SamplerNode::processBlockInternal(AudioBuffer<float>& buffer, MidiBuffer& m
868874 }
869875
870876
871- if (pm == HIT_ONESHOT && s->oneShotted )
872- {
873- s->adsr .applyEnvelopeToBuffer (tmpNoteBuffer, 0 , blockSize);
874- continue ;
875- }
876-
877877 if (pm != HIT_ONESHOT || !s->oneShotted )
878878 {
879879 AudioSampleBuffer* targetBuffer = &s->buffer ;
880880 int targetReadSample = s->playingSample ;
881881
882882 if (s->isProxyNote ())
883883 {
884-
885884 AudioBuffer<float > tmpBuffer (s->autoKeyFromNote ->buffer .getNumChannels (), blockSize);
886885 auto readPointers = tmpBuffer.getArrayOfReadPointers ();
887886
@@ -905,7 +904,6 @@ void SamplerNode::processBlockInternal(AudioBuffer<float>& buffer, MidiBuffer& m
905904 if (pm == HIT_ONESHOT && s->playingSample + blockSize >= sourceNumSamples)
906905 {
907906 s->oneShotted = true ;
908- s->jumpGhostSample = -1 ;
909907 s->playingSample = 0 ;
910908 s->state ->setValueWithData (s->hasContent () ? NoteState::FILLED : NoteState::EMPTY );
911909 break ;
@@ -918,106 +916,101 @@ void SamplerNode::processBlockInternal(AudioBuffer<float>& buffer, MidiBuffer& m
918916 targetReadSample = 0 ;
919917 }
920918
921- if (s->jumpGhostSample != -1 && s->jumpGhostSample != s->playingSample && s->jumpGhostSample < s->buffer .getNumSamples ())
919+ // --- 1. NORMAL PROCESSING ---
920+ if (pm == HIT_ONESHOT && s->oneShotted )
921+ {
922+ // Process release tail silently so the ADSR finishes correctly
923+ tmpNoteBuffer.clear ();
924+ s->adsr .applyEnvelopeToBuffer (tmpNoteBuffer, 0 , blockSize);
925+ }
926+ else
922927 {
923928 const int bufNumSamples = targetBuffer->getNumSamples ();
924- const int ghostFirstPart = jmax (0 , jmin (blockSize, bufNumSamples - s->jumpGhostSample ));
925- const int playFirstPart = jmax (0 , jmin (blockSize, bufNumSamples - s->playingSample ));
926-
927- float ghostGainStart = s->jumpGhostGain ;
928-
929+ const int firstPart = jmax (0 , jmin (blockSize, bufNumSamples - targetReadSample));
930+ const int secondPart = blockSize - firstPart;
931+
929932 for (int j = 0 ; j < buffer.getNumChannels (); j++)
930933 {
931934 tmpNoteBuffer.clear (j, 0 , blockSize);
932935
933- if (playFirstPart > 0 )
934- {
935- tmpNoteBuffer.copyFromWithRamp (j, 0 , targetBuffer->getReadPointer (j, s->playingSample ), playFirstPart, 0 , (float )playFirstPart / blockSize);
936- }
937-
938- if (playFirstPart < blockSize)
936+ if (pm == HIT_ONESHOT || s->isProxyNote ())
939937 {
940- const int playSecondPart = blockSize - playFirstPart;
941- if (pm != HIT_ONESHOT && !s->isProxyNote ())
938+ if (firstPart > 0 )
939+ tmpNoteBuffer.copyFrom (j, 0 , *targetBuffer, j, targetReadSample, firstPart);
940+
941+ if (secondPart > 0 && firstPart > 0 )
942942 {
943- tmpNoteBuffer.addFromWithRamp (j, playFirstPart, targetBuffer->getReadPointer (j, 0 ), playSecondPart, (float )playFirstPart / blockSize, 1 );
943+ const int fadeLen = jmin (64 , firstPart);
944+ tmpNoteBuffer.applyGainRamp (j, firstPart - fadeLen, fadeLen, 1 .0f , 0 .0f );
944945 }
945946 }
947+ else
948+ {
949+ if (firstPart > 0 )
950+ tmpNoteBuffer.copyFrom (j, 0 , *targetBuffer, j, targetReadSample, firstPart);
951+
952+ if (secondPart > 0 )
953+ tmpNoteBuffer.copyFrom (j, firstPart, *targetBuffer, j, 0 , secondPart);
954+ }
946955 }
947-
948- s->adsr .applyEnvelopeToBuffer (tmpNoteBuffer, 0 , blockSize);
949956
957+ s->adsr .applyEnvelopeToBuffer (tmpNoteBuffer, 0 , blockSize);
950958 for (int j = 0 ; j < buffer.getNumChannels (); j++)
951959 {
952- if (ghostFirstPart > 0 )
960+ buffer.addFromWithRamp (j, 0 , tmpNoteBuffer.getReadPointer (j), blockSize, s->prevVelocity , s->velocity );
961+ }
962+
963+ s->prevVelocity = s->velocity ;
964+ s->playingSample += blockSize;
965+
966+ int numSamplesToCheck = s->isProxyNote () ? s->autoKeyFromNote ->buffer .getNumSamples ()
967+ : targetBuffer->getNumSamples ();
968+
969+ if (s->playingSample >= numSamplesToCheck)
970+ {
971+ if (pm == HIT_ONESHOT || s->isProxyNote ())
972+ {
973+ s->oneShotted = true ;
974+ s->adsr .gate (0 );
975+ s->playingSample = 0 ;
976+ }
977+ else
953978 {
954- tmpNoteBuffer. addFromWithRamp (j, 0 , targetBuffer-> getReadPointer (j, s->jumpGhostSample ), ghostFirstPart, ghostGainStart, ghostGainStart * ( 1 . 0f - (( float )ghostFirstPart / blockSize))) ;
979+ s->playingSample %= numSamplesToCheck ;
955980 }
956981 }
957-
958- s->jumpGhostSample = -1 ;
959- }
960- else
961- {
962- const int bufNumSamples = targetBuffer->getNumSamples ();
963- const int firstPart = jmax (0 , jmin (blockSize, bufNumSamples - targetReadSample));
964- const int secondPart = blockSize - firstPart;
965-
966-
967- for (int j = 0 ; j < buffer.getNumChannels (); j++)
968- {
969- tmpNoteBuffer.clear (j, 0 , blockSize);
970-
971-
972- if (pm == HIT_ONESHOT || s->isProxyNote ())
973- {
974- if (firstPart > 0 )
975- tmpNoteBuffer.copyFrom (j, 0 , *targetBuffer, j, targetReadSample, firstPart);
976-
977-
978- if (secondPart > 0 && firstPart > 0 )
979- {
980- const int fadeLen = jmin (64 , firstPart);
981- tmpNoteBuffer.applyGainRamp (j, firstPart - fadeLen, fadeLen, 1 .0f , 0 .0f );
982- }
983- }
984- else
985- {
986- if (firstPart > 0 )
987- tmpNoteBuffer.copyFrom (j, 0 , *targetBuffer, j, targetReadSample, firstPart);
988-
989-
990- if (secondPart > 0 )
991- tmpNoteBuffer.copyFrom (j, firstPart, *targetBuffer, j, 0 , secondPart);
992- }
993- }
994982 }
995983
996- s->adsr .applyEnvelopeToBuffer (tmpNoteBuffer, 0 , blockSize);
997- for (int j = 0 ; j < buffer.getNumChannels (); j++)
984+ // --- 2. INDEPENDENT GHOST FADE LOGIC ---
985+ // Process the fading tail directly into the main buffer (bypassing the new velocity ramp)
986+ if (!s->isProxyNote () && s->jumpGhostSample != -1 && s->jumpGhostFadeRemaining > 0 )
998987 {
999- buffer.addFromWithRamp (j, 0 , tmpNoteBuffer.getReadPointer (j), blockSize, s->prevVelocity , s->velocity );
1000- }
988+ const int bufNumSamples = s->buffer .getNumSamples ();
989+ const int ghostFirstPart = jmax (0 , jmin (blockSize, bufNumSamples - s->jumpGhostSample ));
990+ int processSamples = jmin (ghostFirstPart, s->jumpGhostFadeRemaining );
1001991
1002- s->prevVelocity = s->velocity ;
992+ if (processSamples > 0 )
993+ {
994+ float gainStart = s->jumpGhostGain * ((float )s->jumpGhostFadeRemaining / s->jumpGhostFadeTotal );
995+ float gainEnd = s->jumpGhostGain * ((float )(s->jumpGhostFadeRemaining - processSamples) / s->jumpGhostFadeTotal );
1003996
1004- s->playingSample += blockSize;
997+ // Prevent click if the sample intrinsically ends before the 20ms fade finishes
998+ if (s->jumpGhostSample + processSamples >= bufNumSamples)
999+ gainEnd = 0 .0f ;
10051000
1006- int numSamplesToCheck = s->isProxyNote () ? s->autoKeyFromNote ->buffer .getNumSamples ()
1007- : targetBuffer->getNumSamples ();
1008-
1009- if (s->playingSample >= numSamplesToCheck)
1010- {
1011- if (pm == HIT_ONESHOT || s->isProxyNote ())
1012- {
1013- s->oneShotted = true ;
1014- s->jumpGhostSample = -1 ;
1015- s->adsr .gate (0 );
1016- s->playingSample = 0 ;
1001+ for (int j = 0 ; j < buffer.getNumChannels (); j++)
1002+ {
1003+ buffer.addFromWithRamp (j, 0 , s->buffer .getReadPointer (j, s->jumpGhostSample ), processSamples, gainStart, gainEnd);
1004+ }
1005+
1006+ s->jumpGhostSample += processSamples;
1007+ s->jumpGhostFadeRemaining -= processSamples;
10171008 }
1018- else
1009+
1010+ // Clean up ghost when fade completes or sample ends
1011+ if (s->jumpGhostFadeRemaining <= 0 || s->jumpGhostSample >= bufNumSamples)
10191012 {
1020- s->playingSample %= numSamplesToCheck ;
1013+ s->jumpGhostSample = - 1 ;
10211014 }
10221015 }
10231016 }
@@ -1241,6 +1234,8 @@ void SamplerNode::SamplerNote::reset()
12411234 setAutoKey (nullptr );
12421235 jumpGhostSample = -1 ;
12431236 jumpGhostGain = 0 .0f ;
1237+ jumpGhostFadeTotal = 0 ;
1238+ jumpGhostFadeRemaining = 0 ;
12441239 playingSample = 0 ;
12451240 oneShotted = false ;
12461241 state->setValueWithData (hasContent () ? NoteState::FILLED : NoteState::EMPTY );
0 commit comments