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?
Linked from:
Kitch Membery @Kitch2025-05-01 18:58:42.883ZGreat timing... @Mark_Evans just asked about this also.
I'll get back to you ASAP with the script I made for this. :-)
In reply toScott_Robinson⬆:Kitch Membery @Kitch2025-05-01 19:37:41.280Z2025-05-06 22:51:26.904ZHi @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!
- SScott Robinson @Scott_Robinson
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 :)
Kitch Membery @Kitch2025-05-02 05:07:48.618ZEpic. 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.
- In reply toKitch⬆:MMark Evans @Mark_Evans
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.
- SScott Robinson @Scott_Robinson
Not sure about the subsequent scripts thing, but did you try swapping that particular line (142) to:
sf.ui.proTools.appWaitForActive();
- CIn reply toScott_Robinson⬆:@CoughPro
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?
Chad Wahlbrink @Chad2025-05-06 02:00:39.301Z2025-05-06 09:31:15.136ZHi, @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 ptVersiondeclaration 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";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!
- SScott Robinson @Scott_Robinson
That’s just Kitch doing the lord's work of automatically checking your PT version for you and applying the right action :)