No internet connection
  1. Home
  2. How to
  3. Pro Tools

Add Insert to selected tracks of varying widths

By Scott Robinson @Scott_Robinson
    2025-05-01 18:29:37.452Z

    I’m trying to use Teezio’s plugin loader as part of my session setup to add Softube Console 1 onto all of the new tracks, but it won’t simultaneously load mono & stereo versions onto their appropriate tracks.

    I’m in need of a script to sort out the monos and the stereos and apply the appropriate plugin, either via Teezio’s loader or with no dependencies.

    @Kitch, I think you mentioned on the Hangout that you already have some version of this?

    Solved in post #3, click to view
    • 10 replies

    There are 10 replies. Estimated reading time: 19 minutes

    1. Kitch Membery @Kitch2025-05-01 18:58:42.883Z

      Hi @Scott_Robinson

      Great timing... @Mark_Evans just asked about this also.

      I'll get back to you ASAP with the script I made for this. :-)

      1. In reply toScott_Robinson:
        Kitch Membery @Kitch2025-05-01 19:37:41.280Z2025-05-06 22:51:26.904Z

        Hi @Scott_Robinson & @Mark_Evans,

        Here is the script I created for adding the Softube Console 1 plugin to all the selected tracks (it should work with mono and stereo tracks). I've conducted minimal testing, so let me know if it works for you. :-)

        /* // Use this one for console 1
        const pluginInfo = {
            category: "Softube",
            name: "Console 1",
            targetSlot: 1,
        }; */
        
        // Use this one for Avid EQ3 7-Band ()
        const pluginInfo = {
            category: "Avid",
            name: "EQ3 7-Band",
            targetSlot: 1,
        };
        
        const proTools = sf.ui.proTools;
        proTools.mainWindow.invalidate();
        
        if (!proTools.isRunning) throw "Pro Tools is not running.";
        
        // Get the Pro Tools version in number format
        const ptVersion = proTools.appVersion.split('.').map(Number).slice(0, 2).reduce((p, c, i) => p + Math.pow(100, 2 - i) * c, 0);
        
        // The parent menu item for Inserts has changed the word "plug-in" to "plugin" in PT 24.10.0
        const pluginParentMenuString = ptVersion >= 241000 ? "plugin" : "plug-in";
        
        const trackWidthLookup = {
            "TFMono": {
                parentMenu: pluginParentMenuString,
                suffix: "(mono)",
            },
            "TFStereo": {
                parentMenu: `multichannel ${pluginParentMenuString}`,
                suffix: "(stereo)",
            },
            "Master": {
                parentMenu: `multichannel ${pluginParentMenuString}`,
                suffix: "(stereo)",
            },
        }
        
        function setupRestore() {
            const windowMenuFullName = proTools.children.whoseRole.startsWith("AXMenu").first.children
                .find(t => t.title.invalidate().value.startsWith("Window"))
                .title.invalidate().value;
        
            let restoreValues = null;
        
            const getInitialSetup = () => {
                restoreValues = {
                    isEditWindowFocused: proTools.getMenuItem(windowMenuFullName, `Edit`).isMenuChecked,
                    isInsertAEVisible: proTools.getMenuItem("View", "Edit Window Views", "Inserts A-E").isMenuChecked,
                    isInsertFJVisible: proTools.getMenuItem("View", "Edit Window Views", "Inserts F-J").isMenuChecked,
                    areFloatingWindowsVisible: !proTools.getMenuItem(windowMenuFullName, "Hide All Floating Windows").isMenuChecked,
                }
            };
        
            const doSetupSession = () => {
                proTools.menuClick({ menuPath: [windowMenuFullName, "Edit"], targetValue: "Enable" });
                proTools.menuClick({ menuPath: ["View", "Edit Window Views", "Inserts A-E"], targetValue: "Enable" });
                proTools.menuClick({ menuPath: ["View", "Edit Window Views", "Inserts F-J"], targetValue: "Enable" });
                proTools.menuClick({ menuPath: [windowMenuFullName, "Hide All Floating Windows"], targetValue: "Enable" });
            };
        
            const doRestoreSession = () => {
                sf.ui.proTools.refreshMenuItems();
        
                const { isInsertAEVisible, isInsertFJVisible, areFloatingWindowsVisible } = restoreValues;
                !isInsertAEVisible && proTools.menuClick({ menuPath: ["View", "Edit Window Views", "Inserts A-E"], targetValue: "Disable" });
                !isInsertFJVisible && proTools.menuClick({ menuPath: ["View", "Edit Window Views", "Inserts F-J"], targetValue: "Disable" });
                proTools.menuClick({ menuPath: [windowMenuFullName, "Hide All Floating Windows"], targetValue: areFloatingWindowsVisible ? "Disable" : "Enable" });
            };
        
            const hideFloatingWindows = () => {
                proTools.menuClick({
                    menuPath: [windowMenuFullName, "Hide All Floating Windows"],
                    targetValue: "Enable", onError: "Continue"
                });
            }
        
            return {
                getInitialSetup,
                doSetupSession,
                doRestoreSession,
                hideFloatingWindows,
            }
        }
        
        /** 
         * @param {object} args
         * @param {string[]} args.pluginPath
         * @param {number} args.pluginSlotNumber
         */
        function loadInsert({ pluginPath, pluginSlotNumber }) {
            proTools.selectedTrack.trackInsertOrSendSelect({
                pluginNumber: pluginSlotNumber,
                pluginPath,
                selectForAllSelectedTracks: true,
            });
        
            dismissDialog({
                dialogText: "Do you really want to change the existing Insert",
                buttonName: "Change",
            });
        }
        
        /**
         * @param {Object} obj
         * @param {string} obj.dialogText
         * @param {string} obj.buttonName
         */
        function dismissDialog({ dialogText, buttonName }) {
            const dlg = proTools.confirmationDialog;
        
            dlg.elementWaitFor({ timeout: 300, onError: "Continue" });
        
            if (dlg.children.whoseRole.is("AXStaticText").whoseValue.contains(dialogText).first.exists) {
                dlg.buttons.whoseTitle.is(buttonName).first.elementClick();
        
                dlg.elementWaitFor({ waitForNoElement: true });
            }
        }
        
        /** @param {string} trackName */
        function scrollToTrackToTopOfPtWindow(trackName) {
            proTools.appActivateMainWindow();
        
            var originalClipboardText = sf.clipboard.getText().text || '';
        
            proTools.menuClick({ menuPath: ["Track", "Scroll to Track..."] });
        
            proTools.confirmationDialog.elementWaitFor();
        
            sf.clipboard.setText({ text: trackName });
        
            proTools.menuClick({ menuPath: ['Edit', 'Paste'] });
        
            proTools.confirmationDialog.buttons.whoseTitle.is("OK").first.elementClick();
        
            sf.clipboard.setText({ text: originalClipboardText });
        
            // Wait for scroll to finish
            sf.ui.proTools.appWaitForActive();
        }
        
        function checkForExistingInsert(trackNames, targetSlot) {
            return trackNames.some(name => {
                const track = sf.ui.proTools.trackGetByName({ name }).track;
        
                if (!track) return false;
        
                const insert = track.insertButtons[targetSlot - 1];
        
                return insert.exists && insert.value.value !== "unassigned";
            });
        }
        
        function main() {
            proTools.appActivateMainWindow();
        
            const {
                category,
                name,
                targetSlot,
            } = pluginInfo;
        
            const {
                getInitialSetup,
                doSetupSession,
                doRestoreSession,
                hideFloatingWindows
            } = setupRestore();
        
            getInitialSetup();
        
            doSetupSession();
        
            const tracksInfo = sf.app.proTools.tracks.invalidate().allItems.map(track => ({
                name: track.name,
                isSelected: track.isSelected,
                format: track.format,
                type: track.type,
            }));
        
            const targetTrackNames = tracksInfo.map(t => t.name);
        
            const hasExistingInserts = checkForExistingInsert(targetTrackNames, targetSlot);
        
            if (hasExistingInserts) {
                sf.interaction.displayDialog({
                    buttons: ["Cancel", "OK"],
                    cancelButton: "Cancel",
                    defaultButton: "OK",
                    prompt: `One or more selected tracks have an existing insert on slot number ${pluginInfo.targetSlot}.\n\nPress "OK" to overwrite those inserts.`,
                });
        
                // Small wait is required after Display Dialog (This may not be needed)
                sf.wait({ intervalMs: 100 });
        
                proTools.appActivate();
            }
        
            const selectedTrackNames = tracksInfo.filter(track => track.isSelected).map(track => track.name);
        
            try {
                const tracksGroupedByFormat = {}
        
                tracksInfo.forEach(track => {
        
                    if (track.isSelected) {
        
                        const hasInsertSlots = !["BasicFolder", "Vca", "Midi", "Video", "Master"].includes(track.type);
        
                        const isMasterTrack = track.type === "Master";
        
                        if (hasInsertSlots && !isMasterTrack) {
                            if (tracksGroupedByFormat.hasOwnProperty(track.format)) {
                                tracksGroupedByFormat[track.format].push(track);
                            } else {
                                tracksGroupedByFormat[track.format] = [];
                                tracksGroupedByFormat[track.format].push(track);
                            }
                        } else if (isMasterTrack) {
                            if (tracksGroupedByFormat.hasOwnProperty("Master")) {
                                tracksGroupedByFormat["Master"].push(track);
                            } else {
                                tracksGroupedByFormat["Master"] = [];
                                tracksGroupedByFormat["Master"].push(track);
                            }
                        }
                    }
                });
        
                const trackGroupKeys = Object.keys(tracksGroupedByFormat);
        
                trackGroupKeys.forEach((key, index, array) => {
                    proTools.appActivateMainWindow();
        
                    const targetTrackNames = tracksGroupedByFormat[key].map(track => track.name);
        
                    //sf.ui.proTools.trackGetByName({name:targetTrackNames[0]}).track.trackScrollToView();
                    scrollToTrackToTopOfPtWindow(targetTrackNames[0]);
        
                    sf.app.proTools.selectTracksByName({ trackNames: targetTrackNames });
        
        
                    const { parentMenu, suffix } = trackWidthLookup[key];
        
                    loadInsert({
                        pluginPath: [parentMenu, category, `${name} ${suffix}`],
                        pluginSlotNumber: targetSlot,
                    });
        
                    sf.ui.proTools.appActivateMainWindow();
        
                    if (index !== array.length - 1) hideFloatingWindows();
                });
        
            } catch (err) {
                throw err;
            } finally {
                // Reselect Tracks
                sf.app.proTools.selectTracksByName({ trackNames: selectedTrackNames });
        
                // Restore Insert Slot Visibility
                doRestoreSession();
            }
        }
        
        main();
        

        UPDATED Line 142 to use sf.ui.proTools.appWaitForActive();

        Please note I tested this with Avid EQ3 7-Band, as I don't have a Console 1... Maybe one day :-)

        Rock on!

        Reply1 LikeSolution
        1. SScott Robinson @Scott_Robinson
            2025-05-02 05:00:10.727Z

            Thanks @Kitch, this is really fantastic!

            It did fail on first run, but I swapped out your wait() in scrollToTrackToTopOfWindow with sf.ui.proTools.appWaitForActive();. I don’t know if this makes sense or not but it seemed to work.

            Pushing it aggressively, I was able to make it sporadically over-select many tracks when applying it to tracks in various folders all over the session, but that will never be my use case, and I can’t consistently replicate it. I’d call it a success!

            Also, I can’t recommend Console 1 enough, now that it supports FabFilter and UAD :)

            1. Kitch Membery @Kitch2025-05-02 05:07:48.618Z

              Epic. That change seems sound to me.

              Nice one!!!

              I originally worked on this script for David from Mixbus TV. He also raves about the Console 1.

            2. In reply toKitch:
              MMark Evans @Mark_Evans
                2025-05-02 11:26:23.469Z

                Thanks @Kitch !

                Done some preliminary testing this morning and It sticks at the dialog box as you predicted in the script (LINE 141). Not always but often.

                It also affects post use functionality. some subsequent scripts default to opening the main header menus, top of the screen. Something to look at maybe? maybe the session restore?

                Awesome though. Definitely going to be a huge part of my workflow I imagine.

                1. SScott Robinson @Scott_Robinson
                    2025-05-06 03:17:14.888Z

                    Not sure about the subsequent scripts thing, but did you try swapping that particular line (142) to:

                    sf.ui.proTools.appWaitForActive();
                    
                2. C
                  In reply toScott_Robinson:
                  @CoughPro
                    2025-05-06 01:16:33.140Z

                    hi fellas thanks for laying the groundwork. I'm a total noob and this is my first script, i copied your code and added it as a "script" under "my packages" with a keyboard shortcut {control shift *), even entered the protools version and deleted the code for the eq3 7band ;) the first part looks like this for me:

                    const pluginInfo = {
                        category: "Softube",
                        name: "Console 1",
                        targetSlot: 1,
                    }; */
                    
                    const proTools = sf.ui.proTools;
                    proTools.mainWindow.invalidate();
                    
                    if (!proTools.isRunning) throw "Pro Tools is not running.";
                    
                    const pluginParentMenuString = ptVersion >= 241002 ? "plugin";
                    

                    and i even tried changing (your)line 124 above to the waitforactive() as Scott mentioned. (is that the right line? i don't see a "wait()" like you wrote..)
                    however NOTHING happens when i press "run script" or when i go to pro tools, select, and try my keyboard shortcut..

                    any tips?

                    1. Chad Wahlbrink @Chad2025-05-06 02:00:39.301Z2025-05-06 09:31:15.136Z

                      Hi, @CoughPro,

                      Thanks for the question!

                      The issue is in line 5 of the code you shared above. I would bet that this is showing with a red underline in the code editor and a small red block in the scroll bar of the code editor.

                      The */ is part of a 'block comment' in Kitch's code. Delete that bit.

                      Also, it seems like your code is missing the const ptVersion declaration from Kitch's code. Make sure the rest of your code matches the script from Kitch's post.

                      So the first section should be like this:

                      // Use this one for console 1
                      const pluginInfo = {
                          category: "Softube",
                          name: "Console 1",
                          targetSlot: 1,
                      };
                      
                      const proTools = sf.ui.proTools;
                      proTools.mainWindow.invalidate();
                      
                      if (!proTools.isRunning) throw "Pro Tools is not running.";
                      
                      // Get the Pro Tools version in number format
                      const ptVersion = proTools.appVersion.split('.').map(Number).slice(0, 2).reduce((p, c, i) => p + Math.pow(100, 2 - i) * c, 0);
                      
                      // The parent menu item for Inserts has changed the word "plug-in" to "plugin" in PT 24.10.0
                      const pluginParentMenuString = ptVersion >= 241000 ? "plugin" : "plug-in";
                      
                      1. C@CoughPro
                          2025-05-06 02:34:03.162Z

                          cool thanks, it works now! for some reason i thought I had to find my pro tools version and edit it in there, and change the code to reflect "plugin" vs "plug-in" (how pro tools used to understand the term).. overthinking i guess. Wow awesome!

                          1. SScott Robinson @Scott_Robinson
                              2025-05-06 03:19:16.674Z

                              That’s just Kitch doing the lord's work of automatically checking your PT version for you and applying the right action :)