Create New Stereo Send From Selected Track (First available send-intelligent)
Hey SF users!
I've seen a few scripts around that create mono sends intelligently from a track's sends menu but there is no option on those scripts/presets to create a stereo send. I've tried modifying the existing scripts but have had no luck. Does anyone know how to make a script that would:
- Find first available send on a selected track(s)
- Create a new send (with the option to choose mono or stereo as well as name the new send)
The concept shouldn't be too different from the "add an insert to first available" so I'm hoping its fairly simple to do.
Any help would be really appreciate and I think it would be really useful for a lot of Pro Tools users.
Thanks in advance!
JO
Linked from:
- Chad Wahlbrink @Chad2024-02-16 15:29:58.900Z2024-02-16 17:06:11.775Z
Hey @Jordan_Oorebeek!
I saw you asking about this on these other thread:- Create New Track from Send Menu on Selected Tracks Script Help #post-23
- Aux Track is mono #post-3
I modified the script here from @samuel_henriques so that it creates a new send with the same routing, but doesn't open the plugin search view. This script will start, then have you fill in the name for the send, and then complete the creation of it.let monoOrStereo = 'Mono' /////////////////////////////////////// /** * @param {string} sendAssignment */ function assignToNewTrack(sendAssignment) { const selectedTracks = sf.ui.proTools.selectedTrackNames sf.ui.proTools.trackGetByName({ name: selectedTracks[selectedTracks.length - 1] }).track.groups.whoseTitle.is(getGroup("Sends", sendAssignment)).first.buttons.whoseTitle.is(`Send Assignment ${sendAssignment}`).first.popupMenuSelect({ isShift: true, isOption: true, menuPath: ["new track..."], }); } function moveSendWinOutOfSlots() { sf.ui.proTools.invalidate() const sendWin = sf.ui.proTools.mainTrackOutputWindow.elementWaitFor().element const sendWinPos = sendWin.position.x const sendSlots = sf.ui.proTools.selectedTrack.groups.whoseTitle.is("Inserts A-E").first const sendSlotsPos = sendSlots.position.x if (sendWinPos - sendSlotsPos < 450 && sendSlotsPos - sendWinPos < 170) { sendWin.windowMove({ position: { y: sendWin.position.y, x: sendSlotsPos + 450 } }) } } function getFirstFreeSend() { const selectedTracks = sf.ui.proTools.selectedTrackHeaders const sendLetters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] let sendAssignmentList = [] for (let i = 0; i < selectedTracks.length; i++) { let trackInfo = sf.ui.proTools.trackGetByName({ name: selectedTracks[i].normalizedTrackName }) .track.sendButtons.filter(x => x.value.invalidate().value != "unassigned").map(x => x.title.invalidate().value.split(" ")[2]) sendAssignmentList.push(...trackInfo) } const firstFreeSend = sendLetters.filter(letter => sendAssignmentList.indexOf(letter) === -1)[0] if (firstFreeSend) { return firstFreeSend } else { alert("Must have at least one free send, that is the same for all tracks.") throw 0 }; }; /** * @param {'Insert'|'Send'} slotType * @param {array} [defaultUsedSlots] = [] - If parameter is not used, default is [] */ function getFirstFreeInsertOrSend(slotType, defaultUsedSlots) { let buttons slotType == "Insert" ? buttons = "insertButtons" : buttons = "sendButtons" const selectedTracks = sf.ui.proTools.selectedTrackHeaders const sendLetters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] let sendAssignmentList = defaultUsedSlots || [] try { for (let i = 0; i < selectedTracks.length; i++) { let trackInfo = sf.ui.proTools.trackGetByName({ name: selectedTracks[i].normalizedTrackName }) // .insertButtons or .sendButtons .track[buttons].filter(x => x.value.invalidate().value != "unassigned").map(x => x.title.invalidate().value.split(" ")[2]) sendAssignmentList.push(...trackInfo) } } catch (err) { } const firstFreeSend = sendLetters.filter(letter => sendAssignmentList.indexOf(letter) === -1)[0] if (firstFreeSend) { return firstFreeSend } else { if (slotType == "Insert") { alert(`Must have at least one free ${slotType} slot.`) } else if (slotType == "Send") { alert(`Must have at least one free ${slotType} slot, that is the same for selected tracks.`) } throw 0 }; }; /** * @param {string} sendLetter * @param {'Inserts'|'Sends'} slotType */ function getGroup(slotType, sendLetter) { let group sendLetter.toLowerCase().charCodeAt(0) - 97 <= 4 ? group = `${slotType} A-E` : group = `${slotType} F-J` return group } function newTrackSettings() { const newTrackWin = sf.ui.proTools.windows.whoseTitle.is("New Track").first.elementWaitFor().element const format = newTrackWin.popupButtons.allItems[2]; const type = newTrackWin.popupButtons.allItems[0]; const timeBase = newTrackWin.popupButtons.allItems[1]; if (format.value.invalidate().value != monoOrStereo) { format.popupMenuSelect({ menuPath: [monoOrStereo] }); } if (type.value.invalidate().value != "Aux Input") { type.popupMenuSelect({ menuPath: ["Aux Input"] }); } if (timeBase.value.invalidate().value != "Samples") { timeBase.popupMenuSelect({ menuPath: ["Samples"] }); } newTrackWin.checkBoxes.whoseTitle.is("Create next to current track").first.checkboxSet({ targetValue: "Enable" }); // Wait to name track and press ok newTrackWin.elementWaitFor({ waitType: "Disappear", timeout: -1 }) sf.ui.proTools.invalidate() } /** * @param {'Enable'|'Disable'} targetValue * @param {array} [viewsNames] = [] - If parameter is not used, default is set inside */ function setWindowViews(targetValue, viewsNames) { let views = viewsNames || ["Inserts A-E", "Inserts F-J", "Sends A-E", "Sends F-J"] views.forEach(view => { sf.ui.proTools.menuClick({ menuPath: ["View", "Edit Window Views", view], targetValue: targetValue }) }) } const getSelectedTrackOut = (trackName) => sf.ui.proTools.trackGetByName({ name: trackName }).track.outputPathButton.title.invalidate().value.split('\n')[1].trim(); let insertSlot; let firstFreeSend; function actions() { const originalSelectedTracks = sf.ui.proTools.selectedTrackNames // Save first selected track output on variable const firstSelectedOut = getSelectedTrackOut(originalSelectedTracks[0]) // Get free sends firstFreeSend = getFirstFreeInsertOrSend("Send") // Assign new send to new track assignToNewTrack(firstFreeSend) // New track setting and wait for track name newTrackSettings() // If new send win is blocking the inserts or sends, move win away moveSendWinOutOfSlots() // Solo safe new Aux const selectedTracks = sf.ui.proTools.selectedTrackNames const auxTrack = sf.ui.proTools.trackGetByName({ name: selectedTracks[selectedTracks.length - 1] }).track auxTrack.trackSelect() auxTrack.trackScrollToView() // Aux Tracks now default to solo safe status - so this is irrelevant unless you want your aux return to be NOT solo safed. // auxTrack.buttons.whoseTitle.is("Solo").first.mouseClickElement({ isCommand: true }) // set new aux output if (getSelectedTrackOut(auxTrack.normalizedTrackName) != firstSelectedOut) { auxTrack.trackOutputSelect({ outputSelector: items => items.filter(item => item.element.title.value.includes(firstSelectedOut))[0] }) } // Use if you want to automatically search for a plugin // Get free insert slot after A insertSlot = getFirstFreeInsertOrSend("Insert", ["A"]) /* // Open search... auxTrack.groups.whoseTitle.is(getGroup("Inserts", insertSlot)).first .buttons.whoseTitle.is(`Insert Assignment ${insertSlot}`) .first.popupMenuSelect({ menuPath: ["search..."], }); // Wait to open new plug while (true) { const plugIsOpen = auxTrack.groups.whoseTitle.is(getGroup("Inserts", insertSlot)).first .buttons.whoseTitle.is(`Insert Assignment ${insertSlot}`).first .value.invalidate().value != "unassigned" if (!plugIsOpen) { sf.wait({ intervalMs: 1000 }) } else { break; } } */ sf.ui.proTools.trackSelectByName({ names: originalSelectedTracks }) } function main() { sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.invalidate(); const isActiveInsGroupFEdit = sf.ui.proTools.getMenuItem("View", "Edit Window Views", "Inserts F-J").isMenuChecked const isActiveSendGroupFEdit = sf.ui.proTools.getMenuItem("View", "Edit Window Views", "Sends F-J").isMenuChecked const isEditWin = sf.ui.proTools.focusedWindow.title.value.startsWith("Edit") if (!isEditWin) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Edit"] }) } // Close expanded sends sf.ui.proTools.menuClick({ menuPath: ["View", "Expanded Sends", "None"], targetValue: "Enable" }) setWindowViews("Enable"); try { actions() } catch (err) { throw err } finally { // Go to mix if needed if (!isEditWin) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Mix"] }) } // Close Inserts Views if not needed if (insertSlot.toLowerCase().charCodeAt(0) - 97 <= 4 && (/* !isActiveInsGroupFMix || */ !isActiveInsGroupFEdit)) { // if (!isActiveInsGroupFMix) { setWindowViews("Disable", ["Inserts F-J"]) } if (!isActiveInsGroupFEdit) { setWindowViews("Disable", ["Inserts F-J"]) } } // Close Sends F-J if not needed if (firstFreeSend.toLowerCase().charCodeAt(0) - 97 <= 4 && (/* !isActiveSendGroupFMix || */ !isActiveSendGroupFEdit)) { // if (!isActiveSendGroupFMix) { setWindowViews("Disable", ["Sends F-J"]) } if (!isActiveSendGroupFEdit) { setWindowViews("Disable", ["Sends F-J"]) } } } } main();
- In reply toJordan_Oorebeek⬆:Chad Wahlbrink @Chad2024-02-16 15:39:49.335Z2024-02-16 17:04:36.254Z
@Jordan_Oorebeek, this version will allow you to define a plug-in path at the top of the script if you want to be able to always have it open a specific plug-in.
let pluginPath = ['multichannel plug-in', 'SoundToys', 'EchoBoy (stereo)'] let monoOrStereo = 'Stereo' /////////////////////////////////////// /** * @param {string} sendAssignment */ function assignToNewTrack(sendAssignment) { const selectedTracks = sf.ui.proTools.selectedTrackNames sf.ui.proTools.trackGetByName({ name: selectedTracks[selectedTracks.length - 1] }).track.groups.whoseTitle.is(getGroup("Sends", sendAssignment)).first.buttons.whoseTitle.is(`Send Assignment ${sendAssignment}`).first.popupMenuSelect({ isShift: true, isOption: true, menuPath: ["new track..."], }); } function moveSendWinOutOfSlots() { sf.ui.proTools.invalidate() const sendWin = sf.ui.proTools.mainTrackOutputWindow.elementWaitFor().element const sendWinPos = sendWin.position.x const sendSlots = sf.ui.proTools.selectedTrack.groups.whoseTitle.is("Inserts A-E").first const sendSlotsPos = sendSlots.position.x if (sendWinPos - sendSlotsPos < 450 && sendSlotsPos - sendWinPos < 170) { sendWin.windowMove({ position: { y: sendWin.position.y, x: sendSlotsPos + 450 } }) } } function getFirstFreeSend() { const selectedTracks = sf.ui.proTools.selectedTrackHeaders const sendLetters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] let sendAssignmentList = [] for (let i = 0; i < selectedTracks.length; i++) { let trackInfo = sf.ui.proTools.trackGetByName({ name: selectedTracks[i].normalizedTrackName }) .track.sendButtons.filter(x => x.value.invalidate().value != "unassigned").map(x => x.title.invalidate().value.split(" ")[2]) sendAssignmentList.push(...trackInfo) } const firstFreeSend = sendLetters.filter(letter => sendAssignmentList.indexOf(letter) === -1)[0] if (firstFreeSend) { return firstFreeSend } else { alert("Must have at least one free send, that is the same for all tracks.") throw 0 }; }; /** * @param {'Insert'|'Send'} slotType * @param {array} [defaultUsedSlots] = [] - If parameter is not used, default is [] */ function getFirstFreeInsertOrSend(slotType, defaultUsedSlots) { let buttons slotType == "Insert" ? buttons = "insertButtons" : buttons = "sendButtons" const selectedTracks = sf.ui.proTools.selectedTrackHeaders const sendLetters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] let sendAssignmentList = defaultUsedSlots || [] try { for (let i = 0; i < selectedTracks.length; i++) { let trackInfo = sf.ui.proTools.trackGetByName({ name: selectedTracks[i].normalizedTrackName }) // .insertButtons or .sendButtons .track[buttons].filter(x => x.value.invalidate().value != "unassigned").map(x => x.title.invalidate().value.split(" ")[2]) sendAssignmentList.push(...trackInfo) } } catch (err) { } const firstFreeSend = sendLetters.filter(letter => sendAssignmentList.indexOf(letter) === -1)[0] if (firstFreeSend) { return firstFreeSend } else { if (slotType == "Insert") { alert(`Must have at least one free ${slotType} slot.`) } else if (slotType == "Send") { alert(`Must have at least one free ${slotType} slot, that is the same for selected tracks.`) } throw 0 }; }; /** * @param {string} sendLetter * @param {'Inserts'|'Sends'} slotType */ function getGroup(slotType, sendLetter) { let group sendLetter.toLowerCase().charCodeAt(0) - 97 <= 4 ? group = `${slotType} A-E` : group = `${slotType} F-J` return group } function newTrackSettings() { const newTrackWin = sf.ui.proTools.windows.whoseTitle.is("New Track").first.elementWaitFor().element const format = newTrackWin.popupButtons.allItems[2]; const type = newTrackWin.popupButtons.allItems[0]; const timeBase = newTrackWin.popupButtons.allItems[1]; if (format.value.invalidate().value != monoOrStereo) { format.popupMenuSelect({ menuPath: [monoOrStereo] }); } if (type.value.invalidate().value != "Aux Input") { type.popupMenuSelect({ menuPath: ["Aux Input"] }); } if (timeBase.value.invalidate().value != "Samples") { timeBase.popupMenuSelect({ menuPath: ["Samples"] }); } newTrackWin.checkBoxes.whoseTitle.is("Create next to current track").first.checkboxSet({ targetValue: "Enable" }); // Wait to name track and press ok newTrackWin.elementWaitFor({ waitType: "Disappear", timeout: -1 }) sf.ui.proTools.invalidate() } /** * @param {'Enable'|'Disable'} targetValue * @param {array} [viewsNames] = [] - If parameter is not used, default is set inside */ function setWindowViews(targetValue, viewsNames) { let views = viewsNames || ["Inserts A-E", "Inserts F-J", "Sends A-E", "Sends F-J"] views.forEach(view => { sf.ui.proTools.menuClick({ menuPath: ["View", "Edit Window Views", view], targetValue: targetValue }) }) } const getSelectedTrackOut = (trackName) => sf.ui.proTools.trackGetByName({ name: trackName }).track.outputPathButton.title.invalidate().value.split('\n')[1].trim(); let insertSlot; let firstFreeSend; function actions() { const originalSelectedTracks = sf.ui.proTools.selectedTrackNames // Save first selected track output on variable const firstSelectedOut = getSelectedTrackOut(originalSelectedTracks[0]) // Get free sends firstFreeSend = getFirstFreeInsertOrSend("Send") // Assign new send to new track assignToNewTrack(firstFreeSend) // New track setting and wait for track name newTrackSettings() // If new send win is blocking the inserts or sends, move win away moveSendWinOutOfSlots() // Solo safe new Aux const selectedTracks = sf.ui.proTools.selectedTrackNames const auxTrack = sf.ui.proTools.trackGetByName({ name: selectedTracks[selectedTracks.length - 1] }).track auxTrack.trackSelect() auxTrack.trackScrollToView() // Aux Tracks now default to solo safe status - so this is irrelevant unless you want your aux return to be NOT solo safed. // auxTrack.buttons.whoseTitle.is("Solo").first.mouseClickElement({ isCommand: true }) // set new aux output if (getSelectedTrackOut(auxTrack.normalizedTrackName) != firstSelectedOut) { auxTrack.trackOutputSelect({ outputSelector: items => items.filter(item => item.element.title.value.includes(firstSelectedOut))[0] }) } // Get free insert slot after A insertSlot = getFirstFreeInsertOrSend("Insert", ["A"]) // Open Plugin auxTrack.groups.whoseTitle.is(getGroup("Inserts", insertSlot)).first .buttons.whoseTitle.is(`Insert Assignment ${insertSlot}`) .first.popupMenuSelect({ menuPath: pluginPath, }); // Wait to open new plug while (true) { const plugIsOpen = auxTrack.groups.whoseTitle.is(getGroup("Inserts", insertSlot)).first .buttons.whoseTitle.is(`Insert Assignment ${insertSlot}`).first .value.invalidate().value != "unassigned" if (!plugIsOpen) { sf.wait({ intervalMs: 1000 }) } else { break; } } sf.ui.proTools.trackSelectByName({ names: originalSelectedTracks }) } function main() { sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.invalidate(); const isActiveInsGroupFEdit = sf.ui.proTools.getMenuItem("View", "Edit Window Views", "Inserts F-J").isMenuChecked const isActiveSendGroupFEdit = sf.ui.proTools.getMenuItem("View", "Edit Window Views", "Sends F-J").isMenuChecked const isEditWin = sf.ui.proTools.focusedWindow.title.value.startsWith("Edit") if (!isEditWin) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Edit"] }) } // Close expanded sends sf.ui.proTools.menuClick({ menuPath: ["View", "Expanded Sends", "None"], targetValue: "Enable" }) setWindowViews("Enable"); try { actions() } catch (err) { throw err } finally { // Go to mix if needed if (!isEditWin) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Mix"] }) } // Close Inserts Views if not needed if (insertSlot.toLowerCase().charCodeAt(0) - 97 <= 4 && (/* !isActiveInsGroupFMix || */ !isActiveInsGroupFEdit)) { // if (!isActiveInsGroupFMix) { setWindowViews("Disable", ["Inserts F-J"]) } if (!isActiveInsGroupFEdit) { setWindowViews("Disable", ["Inserts F-J"]) } } // Close Sends F-J if not needed if (firstFreeSend.toLowerCase().charCodeAt(0) - 97 <= 4 && (/* !isActiveSendGroupFMix || */ !isActiveSendGroupFEdit)) { // if (!isActiveSendGroupFMix) { setWindowViews("Disable", ["Sends F-J"]) } if (!isActiveSendGroupFEdit) { setWindowViews("Disable", ["Sends F-J"]) } } } } main()
- JJordan Oorebeek @Jordan_Oorebeek
Thank you so much! Works perfectly! Is there a way to make it so it will automatically do a stereo send? The reason being is I'm attempting to mix without a screen and am hoping to be able to run this and have it auto create the send. Without being able to "see" whether it's mono or stereo and just knowing its going to be set as a mono send command or Stereo send command would be awesome! I'm getting really picky with that though. I'm already thrilled that this script above works.
Chad Wahlbrink @Chad2024-02-16 17:08:12.636Z
@Jordan_Oorebeek, sure thing!
I have updated both scripts
- Create New Stereo Send From Selected Track (First available send-intelligent) #post-2
- Create New Stereo Send From Selected Track (First available send-intelligent) #post-3
You can declare whether you want a mono or stereo send by changing the following at the top of each script:
let monoOrStereo = 'Stereo'
OR
let monoOrStereo = 'Mono'
- JJordan Oorebeek @Jordan_Oorebeek
You rock. Thank you so much! Love this.
- TIn reply toJordan_Oorebeek⬆:Tom Mochiach @Tom_Mochiach
oh great it actually works!!!
can you add an option to add a hardware insert on the track you are sending from, and that the new track would be an audio track?
I want to actually add an outboard and record it on the new track so a record enabled track would also be greatChad Wahlbrink @Chad2025-01-30 20:14:01.487Z
Hi @Tom_Mochiach, I can take a look at that option!
Can you let me know which version of the script above you are using? You can link to the version you'd like me to tweak by clicking the link at the bottom of the posted reply:
Thanks,
Chad- TTom Mochiach @Tom_Mochiach
I did succeed in making it an audio track so here is the script - yes please if you may take it from there-
let pluginPath = ['multichannel plug-in', 'SoundToys', 'EchoBoy (stereo)'] let monoOrStereo = 'Stereo' /////////////////////////////////////// /** * @param {string} sendAssignment */ function assignToNewTrack(sendAssignment) { const selectedTracks = sf.ui.proTools.selectedTrackNames sf.ui.proTools.trackGetByName({ name: selectedTracks[selectedTracks.length - 1] }).track.groups.whoseTitle.is(getGroup("Sends", sendAssignment)).first.buttons.whoseTitle.is(`Send Assignment ${sendAssignment}`).first.popupMenuSelect({ isShift: true, isOption: true, menuPath: ["new track..."], }); } function moveSendWinOutOfSlots() { sf.ui.proTools.invalidate() const sendWin = sf.ui.proTools.mainTrackOutputWindow.elementWaitFor().element const sendWinPos = sendWin.position.x const sendSlots = sf.ui.proTools.selectedTrack.groups.whoseTitle.is("Inserts A-E").first const sendSlotsPos = sendSlots.position.x if (sendWinPos - sendSlotsPos < 450 && sendSlotsPos - sendWinPos < 170) { sendWin.windowMove({ position: { y: sendWin.position.y, x: sendSlotsPos + 450 } }) } } function getFirstFreeSend() { const selectedTracks = sf.ui.proTools.selectedTrackHeaders const sendLetters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] let sendAssignmentList = [] for (let i = 0; i < selectedTracks.length; i++) { let trackInfo = sf.ui.proTools.trackGetByName({ name: selectedTracks[i].normalizedTrackName }) .track.sendButtons.filter(x => x.value.invalidate().value != "unassigned").map(x => x.title.invalidate().value.split(" ")[2]) sendAssignmentList.push(...trackInfo) } const firstFreeSend = sendLetters.filter(letter => sendAssignmentList.indexOf(letter) === -1)[0] if (firstFreeSend) { return firstFreeSend } else { alert("Must have at least one free send, that is the same for all tracks.") throw 0 }; }; /** * @param {'Insert'|'Send'} slotType * @param {array} [defaultUsedSlots] = [] - If parameter is not used, default is [] */ function getFirstFreeInsertOrSend(slotType, defaultUsedSlots) { let buttons slotType == "Insert" ? buttons = "insertButtons" : buttons = "sendButtons" const selectedTracks = sf.ui.proTools.selectedTrackHeaders const sendLetters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] let sendAssignmentList = defaultUsedSlots || [] try { for (let i = 0; i < selectedTracks.length; i++) { let trackInfo = sf.ui.proTools.trackGetByName({ name: selectedTracks[i].normalizedTrackName }) // .insertButtons or .sendButtons .track[buttons].filter(x => x.value.invalidate().value != "unassigned").map(x => x.title.invalidate().value.split(" ")[2]) sendAssignmentList.push(...trackInfo) } } catch (err) { } const firstFreeSend = sendLetters.filter(letter => sendAssignmentList.indexOf(letter) === -1)[0] if (firstFreeSend) { return firstFreeSend } else { if (slotType == "Insert") { alert(`Must have at least one free ${slotType} slot.`) } else if (slotType == "Send") { alert(`Must have at least one free ${slotType} slot, that is the same for selected tracks.`) } throw 0 }; }; /** * @param {string} sendLetter * @param {'Inserts'|'Sends'} slotType */ function getGroup(slotType, sendLetter) { let group sendLetter.toLowerCase().charCodeAt(0) - 97 <= 4 ? group = `${slotType} A-E` : group = `${slotType} F-J` return group } function newTrackSettings() { const newTrackWin = sf.ui.proTools.windows.whoseTitle.is("New Track").first.elementWaitFor().element const format = newTrackWin.popupButtons.allItems[2]; const type = newTrackWin.popupButtons.allItems[0]; const timeBase = newTrackWin.popupButtons.allItems[1]; if (format.value.invalidate().value != monoOrStereo) { format.popupMenuSelect({ menuPath: [monoOrStereo] }); } if (type.value.invalidate().value != "Audio Track") { type.popupMenuSelect({ menuPath: ["Audio Track"] }); } if (timeBase.value.invalidate().value != "Samples") { timeBase.popupMenuSelect({ menuPath: ["Samples"] }); } newTrackWin.checkBoxes.whoseTitle.is("Create next to current track").first.checkboxSet({ targetValue: "Enable" }); // Wait to name track and press ok newTrackWin.elementWaitFor({ waitType: "Disappear", timeout: -1 }) sf.ui.proTools.invalidate() } /** * @param {'Enable'|'Disable'} targetValue * @param {array} [viewsNames] = [] - If parameter is not used, default is set inside */ function setWindowViews(targetValue, viewsNames) { let views = viewsNames || ["Inserts A-E", "Inserts F-J", "Sends A-E", "Sends F-J"] views.forEach(view => { sf.ui.proTools.menuClick({ menuPath: ["View", "Edit Window Views", view], targetValue: targetValue }) }) } const getSelectedTrackOut = (trackName) => sf.ui.proTools.trackGetByName({ name: trackName }).track.outputPathButton.title.invalidate().value.split('\n')[1].trim(); let insertSlot; let firstFreeSend; function actions() { const originalSelectedTracks = sf.ui.proTools.selectedTrackNames // Save first selected track output on variable const firstSelectedOut = getSelectedTrackOut(originalSelectedTracks[0]) // Get free sends firstFreeSend = getFirstFreeInsertOrSend("Send") // Assign new send to new track assignToNewTrack(firstFreeSend) // New track setting and wait for track name newTrackSettings() // If new send win is blocking the inserts or sends, move win away moveSendWinOutOfSlots() // Solo safe new Aux const selectedTracks = sf.ui.proTools.selectedTrackNames const auxTrack = sf.ui.proTools.trackGetByName({ name: selectedTracks[selectedTracks.length - 1] }).track auxTrack.trackSelect() auxTrack.trackScrollToView() // Aux Tracks now default to solo safe status - so this is irrelevant unless you want your aux return to be NOT solo safed. // auxTrack.buttons.whoseTitle.is("Solo").first.mouseClickElement({ isCommand: true }) // set new aux output if (getSelectedTrackOut(auxTrack.normalizedTrackName) != firstSelectedOut) { auxTrack.trackOutputSelect({ outputSelector: items => items.filter(item => item.element.title.value.includes(firstSelectedOut))[0] }) } // Get free insert slot after A insertSlot = getFirstFreeInsertOrSend("Insert", ["A"]) // Open Plugin auxTrack.groups.whoseTitle.is(getGroup("Inserts", insertSlot)).first .buttons.whoseTitle.is(`Insert Assignment ${insertSlot}`) .first.popupMenuSelect({ menuPath: pluginPath, }); // Wait to open new plug while (true) { const plugIsOpen = auxTrack.groups.whoseTitle.is(getGroup("Inserts", insertSlot)).first .buttons.whoseTitle.is(`Insert Assignment ${insertSlot}`).first .value.invalidate().value != "unassigned" if (!plugIsOpen) { sf.wait({ intervalMs: 1000 }) } else { break; } } sf.ui.proTools.trackSelectByName({ names: originalSelectedTracks }) } function main() { sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.invalidate(); const isActiveInsGroupFEdit = sf.ui.proTools.getMenuItem("View", "Edit Window Views", "Inserts F-J").isMenuChecked const isActiveSendGroupFEdit = sf.ui.proTools.getMenuItem("View", "Edit Window Views", "Sends F-J").isMenuChecked const isEditWin = sf.ui.proTools.focusedWindow.title.value.startsWith("Edit") if (!isEditWin) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Edit"] }) } // Close expanded sends sf.ui.proTools.menuClick({ menuPath: ["View", "Expanded Sends", "None"], targetValue: "Enable" }) setWindowViews("Enable"); try { actions() } catch (err) { throw err } finally { // Go to mix if needed if (!isEditWin) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Mix"] }) } // Close Inserts Views if not needed if (insertSlot.toLowerCase().charCodeAt(0) - 97 <= 4 && (/* !isActiveInsGroupFMix || */ !isActiveInsGroupFEdit)) { // if (!isActiveInsGroupFMix) { setWindowViews("Disable", ["Inserts F-J"]) } if (!isActiveInsGroupFEdit) { setWindowViews("Disable", ["Inserts F-J"]) } } // Close Sends F-J if not needed if (firstFreeSend.toLowerCase().charCodeAt(0) - 97 <= 4 && (/* !isActiveSendGroupFMix || */ !isActiveSendGroupFEdit)) { // if (!isActiveSendGroupFMix) { setWindowViews("Disable", ["Sends F-J"]) } if (!isActiveSendGroupFEdit) { setWindowViews("Disable", ["Sends F-J"]) } } } } main()
Chad Wahlbrink @Chad2025-01-30 20:52:43.078Z2025-01-30 22:11:49.574Z
Nice, @Tom_Mochiach,
Below is a new version.
You'll need to replace the
pluginPath
inline 2
with the path of your desired HW. If you'd prefer to search for a piece of hardware each time, I can try to adapt this.let pluginPath = ['i/o', 'H3000 HW (Stereo)']
Also, just a note that you used ''' instead of ``` when you quoted the code. In order to post a script to the forum, you need to use the back ticks (`). In this instance, I just edited your post to format it.
View this tutorial on sharing code on the forum: How to quote code in the SoundFlow forum.
Script:
let hardwareInsert = ['i/o', 'H3000 HW.L (Mono)'] let monoOrStereo = 'Mono' // If Mono, add 4 spaces to the front of the name if(monoOrStereo === 'Mono'){ hardwareInsert = ['i/o', (' ' + hardwareInsert[1])] } /////////////////////////////////////// /** * @param {string} sendAssignment */ function assignToNewTrack(sendAssignment) { const selectedTracks = sf.ui.proTools.selectedTrackNames sf.ui.proTools.trackGetByName({ name: selectedTracks[selectedTracks.length - 1] }).track.groups.whoseTitle.is(getGroup("Sends", sendAssignment)).first.buttons.whoseTitle.is(`Send Assignment ${sendAssignment}`).first.popupMenuSelect({ isShift: true, isOption: true, menuPath: ["new track..."], }); } function moveSendWinOutOfSlots() { sf.ui.proTools.invalidate() const sendWin = sf.ui.proTools.mainTrackOutputWindow.elementWaitFor().element const sendWinPos = sendWin.position.x const sendSlots = sf.ui.proTools.selectedTrack.groups.whoseTitle.is("Inserts A-E").first const sendSlotsPos = sendSlots.position.x if (sendWinPos - sendSlotsPos < 450 && sendSlotsPos - sendWinPos < 170) { sendWin.windowMove({ position: { y: sendWin.position.y, x: sendSlotsPos + 450 } }) } } /** * @param {'Insert'|'Send'} slotType * @param {array} [defaultUsedSlots] = [] - If parameter is not used, default is [] */ function getFirstFreeInsertOrSend(slotType, defaultUsedSlots) { let buttons slotType == "Insert" ? buttons = "insertButtons" : buttons = "sendButtons" const selectedTracks = sf.ui.proTools.selectedTrackHeaders const sendLetters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] let sendAssignmentList = defaultUsedSlots || [] try { for (let i = 0; i < selectedTracks.length; i++) { let trackInfo = sf.ui.proTools.trackGetByName({ name: selectedTracks[i].normalizedTrackName }) // .insertButtons or .sendButtons .track[buttons].filter(x => x.value.invalidate().value != "unassigned").map(x => x.title.invalidate().value.split(" ")[2]) sendAssignmentList.push(...trackInfo) } } catch (err) { } const firstFreeSend = sendLetters.filter(letter => sendAssignmentList.indexOf(letter) === -1)[0] if (firstFreeSend) { return firstFreeSend } else { if (slotType == "Insert") { alert(`Must have at least one free ${slotType} slot.`) } else if (slotType == "Send") { alert(`Must have at least one free ${slotType} slot, that is the same for selected tracks.`) } throw 0 }; }; /** * @param {string} sendLetter * @param {'Inserts'|'Sends'} slotType */ function getGroup(slotType, sendLetter) { let group sendLetter.toLowerCase().charCodeAt(0) - 97 <= 4 ? group = `${slotType} A-E` : group = `${slotType} F-J` return group } function newTrackSettings(trackName) { const newTrackWin = sf.ui.proTools.windows.whoseTitle.startsWith("New Track").first.elementWaitFor().element; const format = newTrackWin.popupButtons.allItems[2]; const type = newTrackWin.popupButtons.allItems[0]; const timeBase = newTrackWin.popupButtons.allItems[1]; if (format.value.invalidate().value != monoOrStereo) { format.popupMenuSelect({ menuPath: [monoOrStereo] }); } if (type.value.invalidate().value != "Audio Track") { type.popupMenuSelect({ menuPath: ["Audio Track"] }); } if (timeBase.value.invalidate().value != "Samples") { timeBase.popupMenuSelect({ menuPath: ["Samples"] }); } newTrackWin.checkBoxes.whoseTitle.is("Create next to current track").first.checkboxSet({ targetValue: "Enable" }); newTrackWin.textFields.whoseTitle.is("Track Name").first.elementSetTextFieldWithAreaValue({ value: trackName }); newTrackWin.buttons.whoseTitle.is("Create").first.elementClick(); newTrackWin.elementWaitFor({ waitType: "Disappear",}); } /** * @param {'Enable'|'Disable'} targetValue * @param {array} [viewsNames] = [] - If parameter is not used, default is set inside */ function setWindowViews(targetValue, viewsNames) { let views = viewsNames || ["Inserts A-E", "Inserts F-J", "Sends A-E", "Sends F-J"] views.forEach(view => { sf.ui.proTools.menuClick({ menuPath: ["View", "Edit Window Views", view], targetValue: targetValue }) }) } const getSelectedTrackOut = (trackName) => sf.ui.proTools.trackGetByName({ name: trackName }).track.outputPathButton.title.invalidate().value.split('\n')[1].trim(); let insertSlot; let firstFreeSend; function actions() { // Original Selected Tracks const originalSelectedTracks = sf.ui.proTools.selectedTrackNames // Save first selected track output on variable const firstSelectedOut = getSelectedTrackOut(originalSelectedTracks[0]) // Add 'pt' to new track name const newTrackName = originalSelectedTracks[0] + ' pt'; // Get free insert slot insertSlot = getFirstFreeInsertOrSend("Insert"); // Open Hardware Insert on Source Track sf.ui.proTools.selectedTrack.groups.whoseTitle.is(getGroup("Inserts", insertSlot)).first .buttons.whoseTitle.is(`Insert Assignment ${insertSlot}`) .first.popupMenuSelect({ menuPath: hardwareInsert, }, `Could not add hardware insert`); // Wait for new Hardware Insert to be Added while (true) { const plugIsOpen = sf.ui.proTools.selectedTrack.groups.whoseTitle.is(getGroup("Inserts", insertSlot)).first .buttons.whoseTitle.is(`Insert Assignment ${insertSlot}`).first .value.invalidate().value != "unassigned" if (!plugIsOpen) { sf.wait({ intervalMs: 1000 }) } else { break; } } // Get free sends on source track firstFreeSend = getFirstFreeInsertOrSend("Send"); // Assign new send to new track assignToNewTrack(firstFreeSend); // New track setting and wait for track name newTrackSettings(newTrackName); // If new send win is blocking the inserts or sends, move win away moveSendWinOutOfSlots(); // Solo safe new Aux const selectedTracks = sf.ui.proTools.selectedTrackNames const auxTrack = sf.ui.proTools.trackGetByName({ name: selectedTracks[selectedTracks.length - 1] }).track auxTrack.trackSelect() auxTrack.trackScrollToView() // Aux Tracks now default to solo safe status - so this is irrelevant unless you want your aux return to be NOT solo safed. // auxTrack.buttons.whoseTitle.is("Solo").first.mouseClickElement({ isCommand: true }) // set new aux output if (getSelectedTrackOut(auxTrack.normalizedTrackName) != firstSelectedOut) { auxTrack.trackOutputSelect({ outputSelector: items => items.filter(item => item.element.title.value.includes(firstSelectedOut))[0] }) } // Set Track Record Enable if (sf.ui.proTools.selectedTrack.buttons.whoseTitle.is("Track Record Enable").first.value.value !== 'on state') { sf.ui.proTools.selectedTrack.buttons.whoseTitle.is("Track Record Enable").first.elementClick(); } // Set Record Track to Mute sf.ui.proTools.selectedTrack.trackSetMute({ targetValue: "Enable" }); // Set Send to Full Volume sf.ui.proTools.mainTrackOutputWindow.invalidate().textFields.whoseTitle.is("Volume Numerical").first.elementSetTextFieldWithAreaValue({ value: '0.0', useMouseKeyboard: true }) sf.keyboard.press({ keys: 'enter' }) sf.ui.proTools.trackSelectByName({ names: originalSelectedTracks }) } function main() { sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.invalidate(); const isActiveInsGroupFEdit = sf.ui.proTools.getMenuItem("View", "Edit Window Views", "Inserts F-J").isMenuChecked const isActiveSendGroupFEdit = sf.ui.proTools.getMenuItem("View", "Edit Window Views", "Sends F-J").isMenuChecked const isEditWin = sf.ui.proTools.focusedWindow.title.value.startsWith("Edit") if (!isEditWin) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Edit"] }) } // Close expanded sends sf.ui.proTools.menuClick({ menuPath: ["View", "Expanded Sends", "None"], targetValue: "Enable" }) setWindowViews("Enable"); try { actions() } catch (err) { throw err } finally { // Go to mix if needed if (!isEditWin) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Mix"] }) } // Close Inserts Views if not needed if (insertSlot.toLowerCase().charCodeAt(0) - 97 <= 4 && (/* !isActiveInsGroupFMix || */ !isActiveInsGroupFEdit)) { // if (!isActiveInsGroupFMix) { setWindowViews("Disable", ["Inserts F-J"]) } if (!isActiveInsGroupFEdit) { setWindowViews("Disable", ["Inserts F-J"]) } } // Close Sends F-J if not needed if (firstFreeSend.toLowerCase().charCodeAt(0) - 97 <= 4 && (/* !isActiveSendGroupFMix || */ !isActiveSendGroupFEdit)) { // if (!isActiveSendGroupFMix) { setWindowViews("Disable", ["Sends F-J"]) } if (!isActiveSendGroupFEdit) { setWindowViews("Disable", ["Sends F-J"]) } } } } main()
Chad Wahlbrink @Chad2025-01-30 21:07:38.557Z
Realizing now I didn't do what you asked! Working on it.
- TTom Mochiach @Tom_Mochiach
Thank you so much- while on that I was thinking that naming the new track with the same name of the original plus, maybe, "pt" for printed (like pro tools does with committed trucks), will be great. then we can skip the stage of naming the track or give it a width or kind (aux audio etc.)
Chad Wahlbrink @Chad2025-01-30 21:43:29.764Z
Okay! I updated the script above.
Create New Stereo Send From Selected Track (First available send-intelligent) #post-12It will now:
- Add a Hardware Insert to the Source Track
- Create a New Aux Print Track using the Track Name + "pt"
- The new Aux is record enabled and muted to prevent doubling up while monitoring
- The send level is set to "0.0" so you can print at "100%"
If I'm missing anything or misunderstanding, please let me know.
Here's a quick video: