Wheelie script running error messages when doing long scrolls for multiple increment changes
Title
Wheelie script running error messages when doing long scrolls for multiple increment changes
Content type
preset
Content name
Scroll Down
Content package
Wheelie (Scroll Faders & Knobs)
Version info
OS: macOS 14.4.1
SoundFlow: 6.1.0
Pro Tools: 25.12.0.127
What do you expect to happen when you run the preset?
Scroll faders down (and up in other script), would only scroll for 1 increment before sending error message for each subsequent scroll attempt
Are you seeing an error?
wheelie failed
What happens when you run the preset?
The scroll will work for a single increment of .5 when trying to lower fader before sending error messages of failing to perform. Very slow scrolls every few seconds seem to work to avoid error message, but it will not scroll down multiple increments with a long scroll
How were you running this preset?
I used a keyboard shortcut within the target app
How important is this issue to you?
5
Details
{
"textExpected": "Scroll faders down (and up in other script), would only scroll for 1 increment before sending error message for each subsequent scroll attempt\n",
"textError": "wheelie failed",
"textWhatHappens": "The scroll will work for a single increment of .5 when trying to lower fader before sending error messages of failing to perform. Very slow scrolls every few seconds seem to work to avoid error message, but it will not scroll down multiple increments with a long scroll",
"inputHowRun": "I used a keyboard shortcut within the target app",
"inputImportance": 5,
"textTitle": "Wheelie script running error messages when doing long scrolls for multiple increment changes"
}
Source
//Preset converted to script
//Invoking preset "Scroll Down" of template "Wheelie" in package "Wheelie (Scroll Faders & Knobs)"
sf.soundflow.runCommand({
commandId: 'user:cmladptig0000x2a8hpyel1bi:ckr3beww20001x010y7r3fe1p',
props: {
action: "Decrement",
delta: -1,
sensitivity: 2,
},
})
Links
User UID: Ps8UFoAArCexzpM6iK3WHE2v9pD3
Feedback Key: sffeedback:Ps8UFoAArCexzpM6iK3WHE2v9pD3:-OklFKxEW_MT0FQJceB5
Feedback ZIP: Z6L4RrUgh10CLwXv0rPw5YEuL1Hb4lYfYIkfuoZRMV+uIP8/csTeGnWMXvliU1MFb+d4XqMBBwHuFu/wvLmgeIDH3LiuAu4AHPmb0PiHVUo9KDy33SgaToKQM8aTGqBQr1QD88+hT65TAGvL6y2KBIKXNMTO8MHojdR7R+5UBTTm7d6UQ0MeqLgDZM5c9TX8aSK1tl6ZATRqDByniYS/d3YJqc73TCwAe85ZS9eUivlv7zQGKFF8gKxpCsPUa4ttbw2KDpprF43mQLSb7bKoakL6gnFnO7s6y6hR0XxWLiRegbx8uooh9+2r/CtH8Ot8xYK9SO9qkjlhERCvsGeL4TI2zRRzSoFz/AXBDu/+IvA=
- AIn reply toAndrew_Davis⬆:Andrew Davis @Andrew_Davis
I have since done some playing around with this script, the problem only seems to arize with the fader and pa knobs. all the knobs and sliders within plugins seem to work, but the track fader and pan knobs within the track window give me the error code, and have all the problems listed above. This problem didnt seem to exist with the PT scroll script, but that doesnt seem to exist anymore. Any help is much appreciated!
In reply toAndrew_Davis⬆:Kitch Membery @Kitch2026-02-09 16:43:40.421ZI'm tagging @Ivan_Che here, who is the owner of the "Wheelie (Scroll Faders & Knobs)" package. Hopefully, they can help you resolve the issue. :-)
In reply toAndrew_Davis⬆:Chad Wahlbrink @Chad2026-03-09 21:03:49.424ZHi, @Andrew_Davis,
In this case, you may be best off making an editable copy of this script by selecting it in the SoundFlow Editor and clicking "Make Editable Copy". This allows you to independently update a copy of the script and apply updates.
See this video for how to do that:
https://www.loom.com/share/eac5e6eb4c8745d4a2cf767c1a333f95Also, here's a modified version of the script which seems to function based on my understanding of the original script here:
// Enable SFX for Pro Tools 2025.10+ (gracefully falls back for older versions) if (sf.ui.useSfx) sf.ui.useSfx(); const action = 'AX' + event.props.action; let sensitivity = event.props.sensitivity; const delta = { I: 1, D: -1 }[action.charAt(2)]; const logFaders = true; const inverseAction = { I: 'AXDecrement', D: 'AXIncrement' }[action.charAt(2)]; const win = sf.ui.proTools.mainTrackOutputWindow; // speed sensitivity const now = Date.now(); if (globalState.wheelieLastScroll !== undefined) { sensitivity = Math.floor(Math.max(1, (50 + (25 * sensitivity) - (now - globalState.wheelieLastScroll)) * 0.1)); } function passScroll() { sf.mouse.scroll({ delta, isControl: true }); throw 0; } function makeAction(e, action, x) { x = x || 1; for (let i = 0; i < (sensitivity * x); i++) { // FIX 1: Wrap elementClick in try-catch to handle stale elements gracefully try { e.elementClick({ actionName: action }); } catch (err) { break; } } } //function that returns if mouse inside frame function inside(frame, mousePos) { return (mousePos.x > frame.x && mousePos.x < frame.x + frame.w) && (mousePos.y > frame.y && mousePos.y < frame.y + frame.h); } if (!win.exists) passScroll(); // no sliders on screen, pass scroll on else { //there are some sliders // if last run was less than 300 ms ago, use that run's element & links // otherwise find it again let e, i, linked, inverse, inverseFR, linkedFR, volume, eJoined; let links = []; if (globalState.wheelieLastScroll === undefined || now - globalState.wheelieLastScroll > 500) { const sliders = win.children.whoseRole.is('AXSlider').allItems; const mousePos = sf.mouse.getPosition().position; // mouse position e = sliders.find((e, n) => { // find matching slider i = n; return inside(e.frame, mousePos); }); if (e === undefined) passScroll(); // no sliders are focused globalState.wheelieLaste = e; // remember slider globalState.wheelieLastIndex = i; // remember index const linkButton = win.buttons.whoseTitle.is("Link").first; linked = linkButton.exists && linkButton.isCheckBoxChecked; const inversePanButton = win.buttons.whoseTitle.is("Inverse Pan").first; inverse = e.title.value.includes('Pan') && !e.title.value.includes('F/R') && inversePanButton.exists && inversePanButton.isCheckBoxChecked; const inverseFRButton = win.buttons.whoseTitle.is("Front/Rear Inverse").first; inverseFR = e.title.value.includes('F/R Pan') && inverseFRButton.exists && inverseFRButton.isCheckBoxChecked; let linkedFRi = [1, 2].includes(i) ? 0 : 1; const linkedFRPanners = win.buttons.whoseTitle.is("Link Front/Rear Left/Right Panners\n").allItems; linkedFR = e.title.value.includes('Pan') && !e.title.value.includes('F/R') && linkedFRPanners[linkedFRi] !== undefined && linkedFRPanners[linkedFRi].exists && linkedFRPanners[linkedFRi].isCheckBoxChecked; globalState.wheelieLastLinked = linked; globalState.wheelieLastInverse = inverse; globalState.wheelieLastInverseFR = inverseFR; globalState.wheelieLastLinkedFR = linkedFR; if (linked) links = sliders.filter((k, n) => k.title.value === e.title.value && n !== i); // find links if (linkedFR) { // if F/R pan linked add more links to the end of array let eJoinedi; eJoined = sliders.find((k, n) => { eJoinedi = n; return k.title.value.includes('Pan Knob') && (n === i - 1 || n === i + 1); }); if (linked && eJoined) { const joinedLink = sliders.find((k, n) => k.title.value === eJoined.title.value && n !== eJoinedi); if (joinedLink) links.push(joinedLink); } } globalState.wheelieLastLinks = links; //remember links globalState.wheelieLasteJoined = eJoined; //remember joined knob // volume for log faders const volumeField = win.textFields.whoseTitle.is("Volume Numerical").first; volume = volumeField.exists ? Number(volumeField.value.value) : -117; if (isNaN(volume)) volume = -117; globalState.wheelieLastVolume = volume; } else { // FIX 2: Re-query fresh element by index after invalidation win.invalidate(); const sliders = win.children.whoseRole.is('AXSlider').allItems; i = globalState.wheelieLastIndex; e = sliders[i]; if (e === undefined) passScroll(); links = globalState.wheelieLastLinks; eJoined = globalState.wheelieLasteJoined; linked = globalState.wheelieLastLinked; inverse = globalState.wheelieLastInverse; inverseFR = globalState.wheelieLastInverseFR; linkedFR = globalState.wheelieLastLinkedFR; volume = globalState.wheelieLastVolume; } // got the focused slider //remember the scroll globalState.wheelieLastScroll = now; //do the thing if (e.title.value.includes('Knob')) { // it's a knob makeAction(e, action); if (linkedFR && eJoined) makeAction(eJoined, action); //const linkFrontRear = win.buttons.whoseTitle.is("Link Front/Rear Left/Right Panners\n").first; if (linked) { // link enabled if (inverse || inverseFR) { // it's a pan knob & inverse is enabled links.forEach(e => makeAction(e, inverseAction)); } else { //just linked, inverse is disabled links.forEach(e => makeAction(e, action)); } } } else if (e.title.value === 'Volume') { // it's a fader let x = 1; if (logFaders) { // let's log it up x = Math.min(8, Math.round(1.2 ** (Math.abs(volume) * 0.15))); //log(globalState.wheelieLastVolume + ' + 0.5 * ' + sensitivity + ' * ' + x + ' * ' + delta); globalState.wheelieLastVolume = Math.max(-117, globalState.wheelieLastVolume + 0.5 * sensitivity * x * delta); } makeAction(e, action, x); } }- AIn reply toAndrew_Davis⬆:Andrew Davis @Andrew_Davis
This seems to have 99% fixed things, thank you! Any idea what modifications would be needed to the script to make any moves able to be undone if need be? I can scroll and change values freely now, but it wont let me undo any value changes. Thanks!
- Progress