I think the sampleImage() function is pretty demanding. And the fact that you recursively use it must be what makes the render so slow. Just think that for the last frame, your expression must calculate all the values before, for each frame.
You could either use the AudioToKeyFrames() to see if that is faster or make a script that sets keyframes each time it finds a kick. Because scripts have memory, you do not have to use while and go back in time each frame.
For the script, you could do it like this.
Check when kick is one and add a keyframe. The script should look similar to this
myComp = app.project.activeItem;
myKickSlider = myComp.layer("Kick count").effect("Slider Control")("Slider");
//delete previous keyframes
for (i = myKickSlider.numKeys; i >= 1; i--) {
myKickSlider.removeKey(i);
}
//add new ones
for (var i = 1; i <= timeToFrames(myComp.duration); i++) {
if (myKickSlider.valueAtTime(framesToTime(i)) == 1) {
myKickSlider.setValueAtTime(framesToTime(i), 1);
}
}
Then the OutputSlider should have an expression similar to this:
kicks = thisComp.layer("Kick count").effect("Slider Control")("Slider");
k = kicks.nearestKey(time);
n = k.index;
if (time < k.time) n--;
addedRotation = inc * n;
base + addedRotation;
You should define base and inc prior to this part.
Haven’t tested these, so they may need a little tweak.