@Lindon Claude says:
The delay is not the real problem. Two things are going on: the interface script is deferred, so its onNoteOn runs after the note has already been rendered, and Gain / Filter are shared effects with parameter smoothing, so every change ramps from the old value and also ramps the tail of the previous hit. Per-note values on one shared effect can never be clean while drums are still ringing.
The per-voice route avoids all of it:
Put a Script Processor in the sampler's MIDI chain (not the deferred interface). It's onNoteOn runs on the audio thread before the sampler sees the event. For gain and pitch use Message.setGain(dB), Message.setCoarseDetune / setFineDetune. They are baked into the event and applied at voice start, no ramp. For the filter (or anything else), use the Event Data Modulator. In the same MIDI processor: const var rm = Engine.getGlobalRoutingManager(); function onNoteOn() { rm.setEventData(Message.getEventId(), 0, nextValue); // slot 0, 0..1 }Then add a polyphonic Filter to the sampler's FX chain and an Event Data Modulator (SlotIndex 0) in its Frequency Modulation chain. The value is captured when that voice starts and never touches older voices. Set DefaultValue to something sensible (it is 0% by default), or make sure every note-on writes the slot. The same modulator in the sampler's Gain and Pitch chains works if you would rather keep everything on one mechanism.
Timers, delayEvent and polling loops all add latency and still leave the previous hit on the wrong setting.
No idea if that's useful, but since you don't use Claude I thought you might want to see it's input.