No internet connection
  1. Home
  2. How to

How to toggle bypass plugins one after another (A/B testing)

By Andrew Sherman @Andrew_Sherman
    2022-06-25 13:16:54.583Z

    I'm currently comparing and demoing specific compression types. I have tweaked each one to match the settings and output levels.

    In Pro Tools, I have a track currently selected. A single plugin is active, the rest are bypassed. If there is more than one plugin active, the other ones should be deactivated.

    When I trigger the script, I want it to

    • bypass the current one
    • close the plugin window
    • activate the next plugin
    • show the next plugin window
    // https://forum.soundflow.org/-939#post-6
    // https://forum.soundflow.org/-4090#post-2
    
    sf.ui.proTools.selectedTrack.trackInsertToggleBypass({
        insertNumber: 6,
    });
    Solved in post #8, click to view
    • 12 replies

    There are 12 replies. Estimated reading time: 17 minutes

    1. samuel henriques @samuel_henriques
        2022-06-28 08:51:10.196Z2022-06-28 09:18:06.632Z

        Hello Andrew,
        Here's my take on this. I'm sure i'll find a better approach code wise, but it's working here.

        Now, as soon as I finished this I realised instead of toggling between all inserts, choosing witch inserts makes more sense, for more common use. You can have a set of plugins and choose between 2 or 3 plug-ins and not all 10 inserts.
        So I'll work on that when I can.
        For now try this and let me know how it woks for you.

        
        /**
        * @param {{ [s: string]: any; }} obj
        */
        function getFirstNOTbypassedSlot(obj) {
            try {
                for (let i = 0; i < 10; i++) {
                    if ((obj[i + 1] == "") || (obj[i + 1] == "open")) { return i + 1; }
                };
            } catch (e) { }
        };
        
        
        /**
        * @param {{track: AxPtTrackHeader;slot: number | string}} arg - Selected Track Header
        */
        function setBypass({ track, slot }) {
        
            track.trackInsertToggleBypass({ insertNumber: Number(slot) });
            track.trackInsertToggleShow({ insertNumber: Number(slot) })
        
        };
        
        
        /** @param {AxPtTrackHeader} track */
        function getInsertSlots(track) {
            const insertSlots = {}
            for (let i = 0; i < 10; i++) {
                try {
                    const slotValue = track.insertSelectorButtons[i].value.invalidate().value
        
                    if (slotValue === "" ||
                        slotValue === "open" ||
                        slotValue.includes("bypassed")) {
        
                        insertSlots[i + 1] = slotValue
                    }
                } catch (e) { }
            };
            return insertSlots;
        };
        
        
        
        function main() {
            const selectedTrack = sf.ui.proTools.selectedTrackHeaders[0]
            if (!selectedTrack) { log("Please Select a Track."); return };
            const insertSlots = getInsertSlots(selectedTrack)
            const numberOfActiveSlots = Object.keys(insertSlots).length
            const numberOfBypassedSlots = Object.values(insertSlots).filter(v => v.includes("bypassed")).length
            const numberOfDisabledBypassSlots = numberOfActiveSlots - numberOfBypassedSlots
            const firstDisabledBypassSlot = getFirstNOTbypassedSlot(insertSlots)
            const firstActiveSlot = Object.keys(insertSlots)[0]
            let indexOfNextKey
            let nextSlot
            try {
                indexOfNextKey = Object.keys(insertSlots).indexOf(firstDisabledBypassSlot.toString()) + 1
                nextSlot = Object.keys(insertSlots)[indexOfNextKey]
            } catch (e) { }
        
        
            // If more than one insert with disabled bypass - bypass all and disable the first
            if (numberOfDisabledBypassSlots > 1) {
        
                for (const key in insertSlots) {
                    // Need to find the NOT bypasses to toggle bypass. 
                    // Otherwise it will toggle the bypassed ones too.
                    if (insertSlots[key] === "" || insertSlots[key] === "open") {
                        setBypass({ track: selectedTrack, slot: key, })
                    }
                };
                setBypass({ track: selectedTrack, slot: firstActiveSlot })
        
                // If None NOT bypassed
            } else if (firstDisabledBypassSlot === undefined) {
        
                setBypass({ track: selectedTrack, slot: firstActiveSlot })
        
                // If last slot
            } else if (indexOfNextKey === numberOfActiveSlots) {
        
                setBypass({ track: selectedTrack, slot: firstDisabledBypassSlot, })
                setBypass({ track: selectedTrack, slot: firstActiveSlot })
        
        
            } else {
                //  Middle slots
                setBypass({ track: selectedTrack, slot: firstDisabledBypassSlot })
                setBypass({ track: selectedTrack, slot: nextSlot })
        
        
            }
        };
        
        
        main()
        
        
        1. AAndrew Sherman @Andrew_Sherman
            2022-06-28 09:06:36.073Z

            That looks amazing Samuel, thank you for this. I'm looking forward to checking it out and learning form the code as well.

            I also think your idea of the different version of the script makes sense, perhaps even being able to switch back and forth between two named plugins on a selected channel (Maybe the script would get the list of plugin names on the channel and ask the user to pick 2 for A/B testing). I would imagine this could be a useful script for some people.

            1. AAndrew Sherman @Andrew_Sherman
                2022-06-28 09:08:02.952Z

                What is this:

                /**
                

                Is it different from a comment in Javascript?

                1. samuel henriques @samuel_henriques
                    2022-06-28 09:21:56.974Z

                    Just made a tiny change in the script.

                    for info on /** check this small lesson from Christian
                    https://forum.soundflow.org/-5018

                    1. AAndrew Sherman @Andrew_Sherman
                        2022-06-28 09:56:16.630Z

                        This link is not accessible for me right now, but it looks like the auto-generated documentation comments (so I don't need the link - thanks)

                        1. samuel henriques @samuel_henriques
                            2022-06-28 10:15:51.364Z

                            yep, sorry didn't notice was on the developers forum.
                            This one is good as well:
                            Using @param

                            1. samuel henriques @samuel_henriques
                                2022-06-28 22:11:37.752Z2022-06-28 22:19:17.748Z

                                Here you go, one script for both occasions.

                                
                                /**
                                * @param {{ [s: string]: any; }} obj
                                */
                                function getFirstDisabledBypassSlot(obj) {
                                    try {
                                        for (let i = 0; i < 10; i++) {
                                            if ((obj[i + 1] == "") || (obj[i + 1] == "open")) { return i + 1; }
                                        };
                                    } catch (e) { }
                                };
                                
                                
                                /**
                                * @param {{track: AxPtTrackHeader, slot: number | string}} arg - Selected Track Header
                                */
                                function setBypass({ track, slot }) {
                                
                                    track.trackInsertToggleBypass({ insertNumber: Number(slot) });
                                    track.trackInsertToggleShow({ insertNumber: Number(slot) });
                                
                                };
                                
                                
                                /**
                                * @param {AxPtTrackHeader} track
                                * @param {string | number[]} inserts
                                */
                                function getInsertSlots(track, inserts) {
                                
                                
                                    const insertSlots = {}
                                    for (let i = 0; i < 10; i++) {
                                        try {
                                            const slotValue = track.insertSelectorButtons[i].value.invalidate().value;
                                            let isActiveSlot = (slotValue === "" || slotValue === "open" || slotValue.includes("bypassed"));
                                
                                            // If array
                                            if (Array.isArray(inserts)) {
                                                if (isActiveSlot && inserts.includes(i + 1)) {
                                                    insertSlots[i + 1] = slotValue;
                                                };
                                
                                                // If String All
                                            } else if (inserts === "All") {
                                                if (isActiveSlot) {
                                                    insertSlots[i + 1] = slotValue;
                                                };
                                            };
                                        } catch (e) { };
                                    };
                                    return insertSlots;
                                };
                                
                                
                                
                                /**
                                * @param {string | number[]} slotsToToggle
                                */
                                function main(slotsToToggle) {
                                
                                    if ((!Array.isArray(slotsToToggle)) && (slotsToToggle != "All")) {
                                        alert(`${slotsToToggle} is Not an Option!\n\nType insert numbers: [1, 2, 3,...10] or "All".`);
                                        return;
                                    };
                                
                                    const selectedTrack = sf.ui.proTools.selectedTrackHeaders[0];
                                    if (!selectedTrack) { alert("Please Select a Track."); return; };
                                
                                    const insertSlots = getInsertSlots(selectedTrack, slotsToToggle);
                                
                                    const numberOfActiveSlots = Object.keys(insertSlots).length;
                                    if (numberOfActiveSlots === 0) { alert("None of Chosen Inserts is Active."); return; };
                                
                                    const numberOfBypassedSlots = Object.values(insertSlots).filter(v => v.includes("bypassed")).length;
                                    const numberOfDisabledBypassSlots = numberOfActiveSlots - numberOfBypassedSlots;
                                    const firstDisabledBypassSlot = getFirstDisabledBypassSlot(insertSlots);
                                    const firstActiveSlot = Object.keys(insertSlots)[0];
                                
                                    let indexOfNextKey
                                    let nextSlot
                                    try {
                                        indexOfNextKey = Object.keys(insertSlots).indexOf(firstDisabledBypassSlot.toString()) + 1;
                                        nextSlot = Object.keys(insertSlots)[indexOfNextKey];
                                    } catch (e) { };
                                
                                
                                    // If more than one insert with disabled bypass - bypass all and disable the first
                                    if (numberOfDisabledBypassSlots > 1) {
                                
                                        for (const key in insertSlots) {
                                            // Need to find the NOT bypasses to toggle bypass. 
                                            // Otherwise it will toggle the bypassed ones too.
                                            if (insertSlots[key] === "" || insertSlots[key] === "open") {
                                                setBypass({ track: selectedTrack, slot: key, });
                                            };
                                        };
                                        setBypass({ track: selectedTrack, slot: firstActiveSlot });
                                
                                        // If all bypassed
                                    } else if (firstDisabledBypassSlot === undefined) {
                                
                                        setBypass({ track: selectedTrack, slot: firstActiveSlot });
                                
                                        // If last slot
                                    } else if (indexOfNextKey === numberOfActiveSlots) {
                                
                                        setBypass({ track: selectedTrack, slot: firstDisabledBypassSlot, });
                                        setBypass({ track: selectedTrack, slot: firstActiveSlot });
                                
                                    } else {
                                        //  Middle slots
                                        setBypass({ track: selectedTrack, slot: firstDisabledBypassSlot });
                                        setBypass({ track: selectedTrack, slot: nextSlot });
                                
                                    }
                                };
                                
                                
                                
                                // "All" to toggle all insert slots
                                // [1,2,3,.. 10] with the insert slot numbers to toggle  
                                
                                main([1, 3, 4])
                                
                                Reply1 LikeSolution
                                1. AAndrew Sherman @Andrew_Sherman
                                    2022-06-29 07:19:41.035Z

                                    This is really excellent Samuel, thank you.

                                    For somebody wanting to use this, you just need to change the numbers in the very last part of the script. For example if you want to compare the plugins on inserts 2, 3 and 5, you change those numbers in the script. Then whenever you trigger the script it will cycle through those options.

                                    Also this script can cycle through all of the inserts if you need that instead.

                                    1. Jordan Pascual @Jordan_Pascual
                                        2024-05-20 10:51:13.417Z

                                        Definitely resurrecting an old thread here, but I LOVE this script... however, I was wondering if it could be more intutive, that instead of putting '1, 3, 4' etc. on line 124 of the script, is there a way we could have Soundflow PROMPT the user via a dialog box what plugins they want to 'A/B'? Then the script would intelligently input those numbers into the function:main script? That way, I don't have to keep coming back to the script to change it every time.

                                        Much appreciated! @samuel_henriques , you're amazing!

                                        J

                                        1. samuel henriques @samuel_henriques
                                            2024-05-20 13:19:06.136Z

                                            Hello Jordan, thank you!
                                            Here you go, replace the last line

                                            main([1, 3, 4])
                                            

                                            with

                                            function promptForInsertNumbers() {
                                            
                                            
                                                let lastUsed = globalState["insertNumberToggleScript"]
                                            
                                                let interaction = sf.interaction.displayDialog({
                                                    buttons: ["Ok", "Cancel"],
                                                    defaultButton: "Ok",
                                                    prompt: "Type numbers between 1 to 10 separated by comma (1,2,3) or space (1 2 3)",
                                                    title: "Inserts Bypass Toggle",
                                                    defaultAnswer: lastUsed ? lastUsed.join(",") : ""
                                                })
                                            
                                            
                                                let insertNumbers = interaction["Text"].split(/[, ]+/).map(e => +e.trim())
                                            
                                                globalState["insertNumberToggleScript"] = insertNumbers
                                            
                                            
                                            };
                                            
                                            // "All" to toggle all insert slots
                                            // [1,2,3,.. 10] with the insert slot numbers to toggle  
                                            
                                            
                                            if (event.keyboardState.hasAlt || !globalState["insertNumberToggleScript"]) {
                                                promptForInsertNumbers()
                                                main(globalState["insertNumberToggleScript"])
                                            } else {
                                                main(globalState["insertNumberToggleScript"])
                                            }
                                            
                                            

                                            Then set a new trigger including ALT

                                            and when you want to change the insert numbers use ALT otherwise it will remember the insert numbers. If you restart SoundFlow, you'll be asked to set the insert number again.

                                            let me know if this is it.

                                            1. Jordan Pascual @Jordan_Pascual
                                                2024-05-20 13:40:24.756Z

                                                ...This is a game-changer. Not only did you implement what I needed, but you went ahead of things and figured the whole 'reset' needed. This works absolutely amazing. Thanks again @samuel_henriques , you are a genius!!!

                                                1. Jordan Pascual @Jordan_Pascual
                                                    2024-05-20 13:46:27.345Z

                                                    One additional quick question @samuel_henriques ... I posted a separate thread regarding this, but it's somewhat related... I've tried isolating how it's done with ONE plugin inside the above code, but I can't figure out how to do it.. especially if I wanted an individual 'selected track' part... when you get a free sec, do you mind looking at this?

                                                    (Bonus if you see this one haha Plugin Replacer - Replace Plugin 'X' with Plugin 'Y')