No internet connection
  1. Home
  2. Packages
  3. Wheelie (Scroll Faders & Knobs)

Wheelie script running error messages when doing long scrolls for multiple increment changes

By Andrew Davis @Andrew_Davis
    2026-02-06 04:41:33.857Z

    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=

    • 5 replies
    1. A
      In reply toAndrew_Davis:
      Andrew Davis @Andrew_Davis
        2026-02-08 01:32:14.891Z

        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!

        1. In reply toAndrew_Davis:
          Kitch Membery @Kitch2026-02-09 16:43:40.421Z

          Hi @Andrew_Davis

          I'm tagging @Ivan_Che here, who is the owner of the "Wheelie (Scroll Faders & Knobs)" package. Hopefully, they can help you resolve the issue. :-)

          1. In reply toAndrew_Davis:
            Chad Wahlbrink @Chad2026-03-09 21:03:49.424Z

            Hi, @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/eac5e6eb4c8745d4a2cf767c1a333f95

            Also, 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);
                }
            
            }
            
            
            1. A
              In reply toAndrew_Davis:
              Andrew Davis @Andrew_Davis
                2026-04-27 02:25:39.651Z

                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!

                1. Progress
                2. S
                  SoundFlow Bot @soundflowbot
                    2026-02-06 17:38:34.387Z
                    SoundFlow support ticket: SFSUP-94