Getting send names on a track
Hi,
I was wondering it would be possible to get the names (bus name) of the sends that are on a selected track and store them to separate variables, or into an array and then to separate variables.
I’d like to build a script that preps some hardware FX print tracks.
Or is there a way to find all tracks that have a particular send assignment?
Thanks in advance
Linked from:
- Raphael Sepulveda @raphaelsepulveda2022-05-06 18:31:51.436Z
Hey @Thomas_Gloor,
Yeah, here's how to get the send names on the selected track to an array:
sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); const selectedTrackSendAssignments = sf.ui.proTools.selectedTrack.sendButtons.map(sendBtn => sendBtn.value.value); log(selectedTrackSendAssignments) // Logs all send assignments
You can then destructure that array to get the assignments into individual variables:
const [sendA, sendB, sendC, sendD, sendE, sendF, sendG, sendH, sendI, sendJ] = selectedTrackSendAssignments; log(sendA) // Logs assignment in Send A
Regarding your second question, yes the script below will find all tracks that contain a certain send.
A few caveats though: 1) It's slow—since it has to go through a lot of data, 2) Only works with visible tracks, and 3) Will only work for the Send columns that are displayed on the Edit window—for example, if you have sends F-J hidden, it won't take those into account.
function getTracksThatContainSendAssignment(sendAssignment) { const allVisibleTracks = sf.ui.proTools.visibleTracks; return allVisibleTracks.trackHeaders.filter(track => { if (!track.groups.whoseTitle.startsWith("Sends").exists) return; return track.sendButtons.some(sendBtn => sendBtn.value.value === sendAssignment) }); } log(getTracksThatContainSendAssignment('Bus 1'))
Hope that helps!
- TThomas Gloor @Thomas_Gloor
Hey @raphaelsepulveda
Thank you very much! It helps tons!
You are right, I don’t think scanning the whole session is a good idea, I think I’ll repeat the script for all selected tracks, which will take time, but the goal of that script is also to take a coffee break :)I’m trying to push the script I’m writing further but I’m struggling a bit, as I think is a bit beyond my JavaScript abilities.
Basically I’d like my script to create tracks only if certain sends are present on a Track. Not slot number, but send (bus) name.
I’m trying out with array.find, but not successfully. As I understand it will find only the first appearance of the first send in the array?
Would you have an idea?
Chris Shaw @Chris_Shaw2022-05-06 23:11:13.281Z
@Thomas_Gloor I might have written a script for what you want to do with this over here:
Show Send lvl on selected track Prompt which send #post-18- In reply toThomas_Gloor⬆:
Raphael Sepulveda @raphaelsepulveda2022-05-06 23:19:55.448Z
Yeah! How about this approach...
/** @param {{ trackHeaders: AxPtTrackHeader[], sendAssignments: string[]}} args */ function getTracksThatContainSendAssignment({ trackHeaders, sendAssignments }) { return trackHeaders.filter(track => { if (!track.groups.whoseTitle.startsWith("Sends").exists) return; return track.sendButtons.some(sendBtn => sendAssignments.includes(sendBtn.value.value)) }); } sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); const tracksWithTargetSendAssignments = getTracksThatContainSendAssignment({ trackHeaders: sf.ui.proTools.selectedTracks.trackHeaders, sendAssignments: ["Blackhole", "Delay Throw"] });
With this script, you can select the tracks you want to analyze send assignments for and it will return an array of the ones that have it within that selection.
Modify thesendAssignments
to whatever you need—this one can handle multiple sends.So once you have that you can do something like this:
if (tracksWithTargetSendAssignments.length >= 1) { // Place code here }
If the array did have one or more tracks that contain your desired send assignments, then run the code inside the
if
statement—in your case, then create tracks script.Hopefully that makes sense.
Chris Shaw @Chris_Shaw2022-05-06 23:27:52.021Z
I see what you're doing here but this will return all tracks that have the send assignment - even if the send or track is inactive.
Raphael Sepulveda @raphaelsepulveda2022-05-07 01:11:47.461Z
Yeah, the script in the link you posted earlier is definitely more robust. I figured I'd provide something simple that Thomas can hopefully customize himself.
Chris Shaw @Chris_Shaw2022-05-07 02:22:11.339Z
Depends on what he needs to do. Yours works if he wants to solo all of the tracks for printing a H/W return, mine works for individual return stems.
As always your code is much nearer than mine.- Comment deleted
- TThomas Gloor @Thomas_Gloor
@raphaelsepulveda
@Chris_Shaw
Thank you both so much for your input. I posted before and deleted my post as it wasn't really clear. I'm gonna try again now :)I'm at the setup stage of my script.
I'm declaring a lot of constants, as follows
. Let user define the name of his hardware FX sends
. Get the Main output level and Main output assignement of every hardware FX return using the
storeHardwareFxReturnLevel
function (we print pre-fader and group with original track later, so this makes sense in my case).I know it's a lot, but it's very clear for me that way.
What I'd like the script to do is the following.
. Scan the selected track for HW sends
. If it finds any of the hardware send defined in the constants, use thecreateStereoFxPrintTrack(hardwarename, hardwareoutput, hardwarevolume)
function for each one of them OR use thecreateMonoFxPrintTrack(hardwarename, hardwareoutput, hardwavevolume)
if the FX Return is MONO. In my case for BINSON and F63 SPRING.
So something like "If send to hardwareFX01 and hardwareFX05 exists, apply createFxPrintTrack with the right parameters).. Move on to the next track and repeat
A couple notes
. I think in my case, it makes way more sense to have the script work for ONE SELECTED TRACK, and then have it repeat for all selected tracks. The reason being, this way it'll be easier to have the print tracks next to the track that had the sends
, Also, I don't really mind if it takes time, even if it is 10/15 minutes. This script will be run after printing the Main passes of a mix, and it will allow me or my assistant to take a well deserved break while it sets up. We then do the prints manually as we can optimise by printing several at the same time, and I don't feel you can automate this :)
I could also run the script for ONE track mid-mix if needed to.My main problem is that I don't understand how to get the "if you found some of the hardware sends in the array, do this" part.
Does that make sense to you guys? Once again, thank you so much for your time.
The code (really unfinished, just trying to have basic constants and functions) is below:
//Declare Hardware FX Template (Name of the FX Return Track, bus and send) const hardwareFX01 = "M7" //<-------- Name of your FX Return Track/Bus/Send goes here const hardwareFX02 = "PCM60" const hardwareFX03 = "ADR68K" const hardwareFX04 = "DN780" const hardwareFX05 = "245" const hardwareFX06 = "245 PDLY" const hardwareFX07 = "246" const hardwareFX08 = "252" const hardwareFX09 = "H3000S" const hardwareFX10 = "DP4 AB" const hardwareFX11 = "DP4 CD" const hardwareFX12 = "BINSON" const hardwareFX13 = "F63 SPRING" //Declare original track selection constant const originalSelection = sf.ui.proTools.selectedTrackNames /* const hardwareFX14 = "HW FX Name here" const hardwareFX15 = "HW FX Name here" const hardwareFX16 = "HW FX Name here" */ //Declare Hardware FX Return Main Output Level function function storeHardwareFxReturnLevel(fxreturn){ sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); sf.ui.proTools.trackSelectByName({names: [fxreturn], deselectOthers: true}); sf.ui.proTools.selectedTrack.trackOutputToggleShow(); sf.ui.proTools.mainTrackOutputWindow.elementWaitFor({waitType: "Appear"}); sf.ui.proTools.mainTrackOutputWindow.textFields.whoseTitle.is('Volume Numerical').first.elementClick(); sf.keyboard.press({keys: "cmd+c"}); sf.ui.proTools.viewCloseFloatingWindows(); } //Welcome message sf.interaction.displayDialog({ hiddenAnswer: true, buttons: ["OK","Cancel"], defaultButton: "OK", cancelButton: "Cancel", title: "Preparing FX Return Print Tracks", prompt: "I will now fetch all the information needed about your hardware FX Returns. Press OK to continue.", giveUpAfterSeconds: 2, }); //Declare FX Return Tracks Main Output Level and Main Output assignment constants storeHardwareFxReturnLevel(hardwareFX01) const hardwareFX01Volume = sf.clipboard.getText().text; const hardwareFX01Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX02) const hardwareFX02Volume = sf.clipboard.getText().text; const hardwareFX02Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX03) const hardwareFX03Volume = sf.clipboard.getText().text; const hardwareFX03Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX04) const hardwareFX04Volume = sf.clipboard.getText().text; const hardwareFX04Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX05) const hardwareFX05Volume = sf.clipboard.getText().text; const hardwareFX05Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX06) const hardwareFX06Volume = sf.clipboard.getText().text; const hardwareFX06Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX07) const hardwareFX07Volume = sf.clipboard.getText().text; const hardwareFX07Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX08) const hardwareFX08Volume = sf.clipboard.getText().text; const hardwareFX08Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX09) const hardwareFX09Volume = sf.clipboard.getText().text; const hardwareFX09Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX10) const hardwareFX10Volume = sf.clipboard.getText().text; const hardwareFX10Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX11) const hardwareFX11Volume = sf.clipboard.getText().text; const hardwareFX11Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX12) const hardwareFX12Volume = sf.clipboard.getText().text; const hardwareFX12Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX13) const hardwareFX13Volume = sf.clipboard.getText().text; const hardwareFX13Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; /* //BACKUP IN CASE YOU HAVE MORE HARDWARE FX storeHardwareFxReturnLevel(hardwareFX14) const hardwareFX14Volume = sf.clipboard.getText().text; const hardwareFX14Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX15) const hardwareFX15Volume = sf.clipboard.getText().text; const hardwareFX15Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; storeHardwareFxReturnLevel(hardwareFX16) const hardwareFX16Volume = sf.clipboard.getText().text; const hardwareFX16Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; */ //Welcome message sf.interaction.displayDialog({ hiddenAnswer: false, buttons: ["OK","Cancel"], defaultButton: "OK", cancelButton: "Cancel", title: "Preparing FX Return Print Tracks", prompt: "Please make sure all tracks are selected and press OK", giveUpAfterSeconds: -1, }); /////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////MAIN FUNCTIONS/////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// function createStereoFxPrintTrack(hardwarename, hardwareoutput, hardwarevolume){ sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); //Get selected track name from comments var trackWithSends = selectedTrackNames[0]; sf.ui.proTools.trackSelectByName({ names: [trackWithSends] }) sf.ui.proTools.menuClick({ menuPath: ["Track","New..."],}); //setup track to be a STEREO AUDIO TRACK in SAMPLES sf.ui.proTools.windows.whoseTitle.is("New Tracks").first.popupButtons.first.popupMenuSelect({menuPath: ["Stereo"],}); sf.ui.proTools.windows.whoseTitle.is("New Tracks").first.popupButtons.allItems[1].popupMenuSelect({menuPath: ["Audio Track"],}); sf.ui.proTools.windows.whoseTitle.is("New Tracks").first.popupButtons.allItems[2].popupMenuSelect({menuPath: ["Samples"],}); //click text field/tab to text field sf.keyboard.press({keys: "tab",}); //enter track name + "PRINT" & confirm sf.keyboard.type({text: trackWithSends + "-" + hardwarename + " PRINT",}); sf.keyboard.press({keys: "return",}); //set input to hardwareFX01 + "PRINT" sf.ui.proTools.selectedTrack.trackInputSelect({inputSelector: items => items.filter(item => item.path.slice(-1)[0].includes(hardwarename + " PRINT"))[0],}); //set output to hardwareFX01Output sf.ui.proTools.selectedTrack.trackOutputSelect({ outputSelector: items => items.filter(item => item.path.slice(-1)[0].includes(hardwareoutput))[0],}); //color track sf.ui.proTools.colorsSelect({colorType: "Color", colorBrightness: "Medium", colorNumber: 16,}); //set fader level to hardwareFX01Volume sf.ui.proTools.selectedTrack.trackOutputToggleShow(); sf.ui.proTools.mainTrackOutputWindow.elementWaitFor({waitType: "Appear"}); sf.ui.proTools.mainTrackOutputWindow.textFields.whoseTitle.is('Volume Numerical').first.elementClick(); sf.keyboard.type({ text: hardwarevolume }); sf.ui.proTools.viewCloseFloatingWindows(); //Go back to track with sends sf.ui.proTools.trackSelectByName({ names: [trackWithSends] }) } /* createStereoFxPrintTrack({ hardwarename : hardwareFX01, hardwareoutput : hardwareFX01Output, hardwarevolume : hardwareFX01Volume, }); */ function createMonoFxPrintTrack(hardwarename, hardwareoutput, hardwarevolume){ sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); //Get selected track name from comments var trackWithSends = selectedTrackNames[0]; sf.ui.proTools.trackSelectByName({ names: [trackWithSends] }) sf.ui.proTools.menuClick({ menuPath: ["Track","New..."],}); //setup track to be a STEREO AUDIO TRACK in SAMPLES sf.ui.proTools.windows.whoseTitle.is("New Tracks").first.popupButtons.first.popupMenuSelect({menuPath: ["Mono"],}); sf.ui.proTools.windows.whoseTitle.is("New Tracks").first.popupButtons.allItems[1].popupMenuSelect({menuPath: ["Audio Track"],}); sf.ui.proTools.windows.whoseTitle.is("New Tracks").first.popupButtons.allItems[2].popupMenuSelect({menuPath: ["Samples"],}); //click text field/tab to text field sf.keyboard.press({keys: "tab",}); //enter track name + "PRINT" & confirm sf.keyboard.type({text: trackWithSends + "-" + hardwarename + " PRINT",}); sf.keyboard.press({keys: "return",}); //set input to hardwareFX01 + "PRINT" sf.ui.proTools.selectedTrack.trackInputSelect({inputSelector: items => items.filter(item => item.path.slice(-1)[0].includes(hardwarename + " PRINT"))[0],}); //set output to hardwareFX01Output sf.ui.proTools.selectedTrack.trackOutputSelect({ outputSelector: items => items.filter(item => item.path.slice(-1)[0].includes(hardwareoutput))[0],}); //color track sf.ui.proTools.colorsSelect({colorType: "Color", colorBrightness: "Medium", colorNumber: 16,}); //set fader level to hardwareFX01Volume sf.ui.proTools.selectedTrack.trackOutputToggleShow(); sf.ui.proTools.mainTrackOutputWindow.elementWaitFor({waitType: "Appear"}); sf.ui.proTools.mainTrackOutputWindow.textFields.whoseTitle.is('Volume Numerical').first.elementClick(); sf.keyboard.type({ text: hardwarevolume }); sf.ui.proTools.viewCloseFloatingWindows(); //Go back to track with sends sf.ui.proTools.trackSelectByName({ names: [trackWithSends] }) } /* createMonoFxPrintTrack({ hardwarename : hardwareFX01, hardwareoutput : hardwareFX01Output, hardwarevolume : hardwareFX01Volume, }); */ /////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////MAIN SCRIPT////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// /* function doForAllSelectedTracks(action) { try { sf.ui.proTools.selectedTrackHeaders.forEach(track => { track.trackSelect(); action(track); }); } finally { sf.ui.proTools.trackSelectByName({ names: originalSelection }); } } function prepFxPrint(track) { //Main script goes here } doForAllSelectedTracks(prepFxPrint); */
Raphael Sepulveda @raphaelsepulveda2022-05-07 16:26:03.770Z
@Thomas_Gloor, yeah, that makes sense! Seeing the full script definitely helps see the whole picture. It can be streamlined significantly.
I can help you with this when I get some time next week unless someone else gets to it first!
- TThomas Gloor @Thomas_Gloor
@raphaelsepulveda
Thank you very much man! Appreciate it a lot.I’m
Sure it can be streamlined, but for now I see it more clearly and remember, I suck at arrays haha.One thing that you could help
Me with maybe is that the new track function doesn’t seem to understand the hardwaname parameter as a string. It spits put [object Object]. I tried to useto.String
, also to infer parameter types. But nothing will do...I'm doint this in the following test script:const hardwareFX01 = "245"; const selectedTrackNames = sf.ui.proTools.selectedTrackNames function storeHardwareFxReturnLevel(fxreturn){ sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); sf.ui.proTools.trackSelectByName({names: [fxreturn], deselectOthers: true}); sf.ui.proTools.selectedTrack.trackOutputToggleShow(); sf.ui.proTools.mainTrackOutputWindow.elementWaitFor({waitType: "Appear"}); sf.ui.proTools.mainTrackOutputWindow.textFields.whoseTitle.is('Volume Numerical').first.elementClick(); sf.keyboard.press({keys: "cmd+c"}); sf.ui.proTools.viewCloseFloatingWindows(); } storeHardwareFxReturnLevel(hardwareFX01) const hardwareFX01Volume = sf.clipboard.getText().text; const hardwareFX01Output = sf.ui.proTools.selectedTrack.outputPathButton.value.invalidate().value; function createFxPrintTrack(hardwarename, hardwareoutput, hardwarevolume){ sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); //Get selected track name from comments var trackWithSends = selectedTrackNames[0]; var printTrackName = trackWithSends + "-" + hardwarename + " PRINT" var printBusName = hardwarename + " PRINT" sf.ui.proTools.trackSelectByName({ names: [trackWithSends] }) sf.ui.proTools.menuClick({ menuPath: ["Track","New..."],}); //setup track to be a STEREO AUDIO TRACK in SAMPLES sf.ui.proTools.windows.whoseTitle.is("New Tracks").first.popupButtons.first.popupMenuSelect({menuPath: ["Stereo"],}); sf.ui.proTools.windows.whoseTitle.is("New Tracks").first.popupButtons.allItems[1].popupMenuSelect({menuPath: ["Audio Track"],}); sf.ui.proTools.windows.whoseTitle.is("New Tracks").first.popupButtons.allItems[2].popupMenuSelect({menuPath: ["Samples"],}); //click text field/tab to text field sf.keyboard.press({keys: "tab",}); //enter track name + "PRINT" & confirm sf.keyboard.type({text: printTrackName}); sf.keyboard.press({keys: "return",}); //Copy print track name to comments sf.ui.proTools.selectedTrack.trackOpenRenameDialog(); sf.keyboard.press({ keys: "cmd+c, tab, cmd+v, return",}); //set input to hardwareFX01 + "PRINT" sf.ui.proTools.selectedTrack.trackInputSelect({inputSelector: items => items.filter(item => item.path.slice(-1)[0].includes(printBusName))[0],}); //set output to hardwareFX01Output sf.ui.proTools.selectedTrack.trackOutputSelect({ outputSelector: items => items.filter(item => item.path.slice(-1)[0].includes(hardwareoutput))[0],}); //color track sf.ui.proTools.colorsSelect({colorType: "Color", colorBrightness: "Medium", colorNumber: 16,}); //set fader level to hardwareFX01Volume sf.ui.proTools.selectedTrack.trackOutputToggleShow(); sf.ui.proTools.mainTrackOutputWindow.elementWaitFor({waitType: "Appear"}); sf.ui.proTools.mainTrackOutputWindow.textFields.whoseTitle.is('Volume Numerical').first.elementClick(); sf.keyboard.type({ text: hardwarevolume }); sf.ui.proTools.viewCloseFloatingWindows(); //Go back to track with sends sf.ui.proTools.trackSelectByName({ names: [trackWithSends] }) } createFxPrintTrack({ hardwarename : hardwareFX01, hardwareoutput : hardwareFX01Output, hardwarevolume : hardwareFX01Volume, });
- TThomas Gloor @Thomas_Gloor
@raphaelsepulveda ill update the code if I get time to work on it tomorrow!
- In reply toThomas_Gloor⬆:TThomas Gloor @Thomas_Gloor
I've updated the code and added a request.
I modified the
createFxPrintTrack();
function intocreateStereoFxPrintTrack();
andcreateMonoFxPrintTrack();
because I have a couple hardware FX's that are mono (BINSON & F63 SPRING).
So if sends to these machines are found, I'd like the script to create MONO print tracks.Let me know :)
- In reply toThomas_Gloor⬆:Raphael Sepulveda @raphaelsepulveda2022-05-10 02:11:31.242Z2022-05-17 21:22:53.188Z
@Thomas_Gloor, alright give this a try!
Now all you have to worry about is updating the
hardwareFXNames
array. The script will handle the rest!Hopefully, it all works, let me know if I missed anything.
//Declare Hardware FX Template (Name of the FX Return Track, bus and send) const hardwareFXNames = [ //<-------- Name of your FX Return Track/Bus/Send goes here "M7", "PCM60", "ADR68K", "DN780", "245", "245 PDLY", "246", "252", "H3000S", "DP4 AB", "DP4 CD", "BINSON", "F63 SPRING", ]; // GROUP EXCEPTIONS - Names of tracks to be excluded from grouping const exceptionTrackNames = []; // Fixed track names go here const wildcardExceptions = ["*LEAD* SEND"]; // Track names with wildcards go here. This first pattern covers track names such as "LEAD // SEND", "LEAD VERB SEND", "H-LEAD // SEND", "H-LEAD VERB SEND" // Source: https://forum.soundflow.org/-1012#post-7 function matchWildcard(str, rule) { const escapeSpecialRegexCharacters = (str) => str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); const regEx = new RegExp("^" + rule.split("*").map(escapeSpecialRegexCharacters).join(".*") + "$", "i"); return regEx.test(str); } function getHardwareFxData() { return hardwareFXNames.reduce((acc, name) => { const track = sf.ui.proTools.trackGetByName({ name }).track; // Returns null if track doesn't exist if (track) { acc[name] = { volume: getTrackVolume({ track }), width: getTrackWidth({ track }), output: track.outputPathButton.value.invalidate().value } } return acc; }, {}); } /** @param {{ track: AxPtTrackHeader, action: function }} args */ function doWithTrackOutputWindow({ track, action }) { const outputWin = sf.ui.proTools.mainTrackOutputWindow; if (outputWin.exists) { outputWin.windowClose(); } track.trackOutputToggleShow(); outputWin.elementWaitFor({ waitType: "Appear" }); action(outputWin); outputWin.windowClose(); outputWin.elementWaitFor({ waitType: "Disappear" }); } /** @param {{ track: AxPtTrackHeader }} arg */ function getTrackVolume({ track }) { let fxReturnVol; doWithTrackOutputWindow({ track, action: outputWin => { fxReturnVol = outputWin.textFields.whoseTitle.is("Volume Numerical").first.value.invalidate().value; } }); return fxReturnVol; } /** @param {{ track?: AxPtTrackHeader }} arg */ function getTrackWidth({ track } = {}) { const panSliderAmount = track.groups.whoseTitle.is("Audio IO").first.sliders.whoseTitle.startsWith("Audio ").allItems.count; if (panSliderAmount === 1) { return 'Mono'; } else { return 'Stereo'; } } /** @param {{ trackNames: string[] }} arg */ function getTracksSends({ trackNames }) { return trackNames.reduce((acc, trackName) => { const track = sf.ui.proTools.trackGetByName({ name: trackName }).track; if (track.groups.whoseTitle.startsWith("Sends").exists) { const sendAssignments = track.sendButtons.map((sendBtn, i) => { if (track.sendSelectorButtons[i].value.value.includes("inactive")) { return "inactive"; } else { return sendBtn.value.value } }); acc[trackName] = sendAssignments; } return acc; }, {}); } /** @param {{ name: string, width: 'Stereo' | 'Mono' }} args */ function createNewTrack({ name, width }) { const newTrackWin = sf.ui.proTools.windows.whoseTitle.is("New Tracks").first; sf.ui.proTools.menuClick({ menuPath: ["Track", "New..."], }); newTrackWin.elementWaitFor(); newTrackWin.popupButtons.first.popupMenuSelect({ menuPath: [width], }); newTrackWin.popupButtons.allItems[1].popupMenuSelect({ menuPath: ["Audio Track"], }); newTrackWin.popupButtons.allItems[2].popupMenuSelect({ menuPath: ["Samples"], }); newTrackWin.textFields.whoseTitle.is("Track Name").first.elementSetTextFieldWithAreaValue({ value: name }); newTrackWin.buttons.whoseTitle.is("Create").first.elementClick(); newTrackWin.elementWaitFor({ waitType: "Disappear" }); sf.ui.proTools.mainWindow.invalidate(); } /** @param {{ track?: AxPtTrackHeader, value: string }} args */ function writeToComments({ track = sf.ui.proTools.invalidate().selectedTrack, value }) { const commentsField = track.textFields.whoseTitle.startsWith("Comments ").first; const doesCommentsColumnExist = commentsField.exists; if (doesCommentsColumnExist) { commentsField.elementSetTextFieldWithAreaValue({ useMouseKeyboard: true, value }); sf.keyboard.press({ keys: "return" }); } else { track.trackOpenRenameDialog(); const trackName = track.normalizedTrackName; const renameDialog = sf.ui.proTools.windows.whoseTitle.is(trackName).first; renameDialog.textFields.allItems[1].elementSetTextFieldWithAreaValue({ value }); renameDialog.buttons.whoseTitle.is("OK").first.elementClick(); renameDialog.elementWaitFor({ waitType: "Disappear" }); } } /**@param {{ groupName?: string, groupType?: 'Edit' | 'Mix' | 'Mix/Edit' }} obj */ function createGroup({ groupName, groupType = 'Mix/Edit' } = {}) { const createGroupDialog = sf.ui.proTools.createGroupDialog; sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.menuClick({ menuPath: ["Track", "Group..."] }); createGroupDialog.elementWaitFor(); // Insert group name if (groupName !== undefined) { createGroupDialog.textFields.first.elementSetTextFieldWithAreaValue({ value: groupName }); } // Select Group Type sf.ui.proTools.createGroupDialog.radioButtons.whoseTitle.is(groupType).first.elementClick(); createGroupDialog.buttons.whoseTitle.is('OK').first.elementClick(); createGroupDialog.elementWaitFor({ waitType: "Disappear" }); } function groupPrintTracks({ trackName }) { sf.ui.proTools.trackSelectByName({ names: [ trackName, ...sf.ui.proTools.visibleTrackNames.filter(name => name.startsWith(`${trackName}-`)), ] }); createGroup({ groupName: `${trackName}+FX` }); } function main() { //Welcome message sf.interaction.displayDialog({ title: "Preparing FX Return Print Tracks", prompt: "I will now fetch all the information needed about your hardware FX Returns. Press OK to continue.", buttons: ["OK", "Cancel"], defaultButton: "OK", cancelButton: "Cancel", hiddenAnswer: true, giveUpAfterSeconds: 2, }); sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); // Get Hardware FX Return Tracks Main Output Level, Main Output and Width (Mono or Stereo) const hardwareFx = getHardwareFxData(); // Wait for user to select tracks sf.interaction.displayDialog({ title: "Preparing FX Return Print Tracks", prompt: "Please make sure all tracks are selected and press OK", buttons: ["OK", "Cancel"], defaultButton: "OK", cancelButton: "Cancel", hiddenAnswer: false, giveUpAfterSeconds: -1, }); sf.ui.proTools.appActivateMainWindow(); const selectedTrackNames = sf.ui.proTools.selectedTrackNames; const trackSends = getTracksSends({ trackNames: selectedTrackNames }); const tracksWithTargetSends = function () { return Object.keys(trackSends).reduce((acc, track) => { const doesTrackContainTargetSends = trackSends[track].some(sendAssignment => { return hardwareFXNames.includes(sendAssignment) }); if (doesTrackContainTargetSends) { acc[track] = trackSends[track]; } return acc; }, {}); }(); // Create Print Tracks for (const trackName in tracksWithTargetSends) { tracksWithTargetSends[trackName].reverse().forEach(sendName => { if (!hardwareFXNames.includes(sendName)) return; // EXCEPTIONS ////////////////////////////////////////////////////////////////// // If send is "245 PDLY" then setup a "245" print track instead if (sendName === "245 PDLY") sendName = "245"; //////////////////////////////////////////////////////////////////////////////// const hardware = hardwareFx[sendName]; const printTrackName = trackName + "-" + sendName + " PRINT" const printBusName = sendName + " PRINT"; sf.ui.proTools.trackSelectByName({ names: [trackName] }); //create a new STEREO or MONO AUDIO TRACK in SAMPLES createNewTrack({ name: printTrackName, width: hardware.width }); sf.ui.proTools.selectedTrack.trackScrollToView(); //Copy print track name to comments writeToComments({ value: printTrackName }); //color track sf.ui.proTools.colorsSelect({ colorType: "Color", colorBrightness: "Medium", colorNumber: 16, }); //set input to hardwareFX + "PRINT" sf.ui.proTools.selectedTrack.trackInputSelect({ inputSelector: items => items.find(item => item.path .slice(-1)[0] .includes(printBusName) ), }); //set output to hardwareFX Output sf.ui.proTools.selectedTrack.trackOutputSelect({ outputSelector: items => items.find(item => item.path.slice(-1)[0].includes(hardware.output)), }); //set fader level to hardwareFX Volume doWithTrackOutputWindow({ track: sf.ui.proTools.selectedTrack, action: outputWin => { outputWin.textFields.whoseTitle.is('Volume Numerical').first.elementClick(); sf.keyboard.type({ text: hardware.volume }); sf.keyboard.press({ keys: "return" }); } }); }); const isGroupException = function () { return exceptionTrackNames.includes(trackName) || wildcardExceptions.some(we => matchWildcard(trackName, we)) }(); if (!isGroupException) { groupPrintTracks({ trackName }); } } alert("Creation of hardware Fx print tracks completed.") } main();
- TThomas Gloor @Thomas_Gloor
MAN. I'm speachless. This script is AMAZING. I tried it a bit, and it seems to be working perfectly. I am so thankful you can't imagine. It will make me gain a ridiculous amount of time. Thank you so much for taking the time!!!
I just thought of a feature, that I'm sure you wont have trouble figuring out.
Would it be possible that the script does the following, per track:
. Select the source track (the one with sends)
. Select all the prints relative to the source track
. Creates a mix/edit group and names it NAME OF THE TRACK+FXLastly, there is a special thing. The hardware FX "245 PDLY" is in fact an aux that then feed the "245" aux. So if the script finds "245 PDLY" it should in setup a "245" print track. Would there be a solution for this? I'm also trying to find out if I can modify my template to make this easier.
Let me know!
And once again, thank you soooo much!
- TThomas Gloor @Thomas_Gloor
I tried to deal with the "245 PDLY" situation by doing so, but It didn't seem to work. It doesnt find the input routing.
const hardware = hardwareFx[sendName]; var printTrackName = trackName + "-" + sendName + " PRINT" var printBusName = sendName + " PRINT"; if (sendName === "245 PDLY") { var printBusName = "245 PRINT"; var printTrackName = trackName + "-" + printBusName; }
Raphael Sepulveda @raphaelsepulveda2022-05-10 21:22:13.178Z
@Thomas_Gloor, glad it worked out!
Sure thing. I updated the script above to include your new requests!
- TThomas Gloor @Thomas_Gloor
Wow! Thats amazing! Thank you so much, it will really be of a lot of help.
I noticed something tough. The script takes in account INACTIVE hardware sends. Is there a way to prevent that? I have some sends that are always there, and that I activate if needed.
Also, can I bother you with one last request, that would be a separate function, outside of this script?
Would there be a way, after all the hardware prints are done, to make the hardware sends inactive?
Thank you again!
Raphael Sepulveda @raphaelsepulveda2022-05-10 23:58:53.164Z
Ah! Forgot about that lol Updated the code above to ignore inactive sends.
Regarding your other request, I'll see if I can make it happen next time I get a chance!
- TThomas Gloor @Thomas_Gloor
@raphaelsepulveda
Thank you very much!Not only your scripts are amazing but it helps me a lot to learn how to code! So thanks again!
- In reply toraphaelsepulveda⬆:TThomas Gloor @Thomas_Gloor
If you could point in the direction of what to do for my request, I could try to make it work. I have to do some of the work too :D
Raphael Sepulveda @raphaelsepulveda2022-05-11 20:37:05.249Z
Yeah!
Ok, I'd start by putting together a function that makes the sends inactive. You can find that here Make specific send slots inactive
The next thing would be finding the tracks that contain the hardware fx and fetching their sends—the code for that is in this thread.
Finally, it would be a matter of iterating through the tracks and their sends and inactivating the ones that meet the criteria. Very similar to my script above, but instead of creating print tracks, it's inactivating sends!- TThomas Gloor @Thomas_Gloor
Thank you @raphaelsepulveda !
I will try to work on it and post my progress.
One question, should I create a thread for your “prep print tracks” script so it’s easier to find for people?
Raphael Sepulveda @raphaelsepulveda2022-05-11 21:02:49.562Z
Yeah, that sounds good!
- TThomas Gloor @Thomas_Gloor
Will do as soon as I’m in front of a computer!
- In reply toraphaelsepulveda⬆:TThomas Gloor @Thomas_Gloor
I used your function to determine a track's width in another script, and run into a little problem.
It works for tracks that are routed to a STEREO bus, and checks how many pan sliders are there, but if a MONO track goes to a MONO bus, there is no pan slider.
I modified it so it works in that case. See below.
function getTrackWidth({ track } = {}) { const panSliderAmount = track.groups.whoseTitle.is("Audio IO").first.sliders.whoseTitle.startsWith("Audio ").allItems.count; if (panSliderAmount === 1) { return 'Mono'; } else if (panSliderAmount === 0) { return 'Mono'; } else { return 'Stereo'; } } ```
Raphael Sepulveda @raphaelsepulveda2022-05-11 20:37:17.318Z
Good catch!
- TThomas Gloor @Thomas_Gloor
Thanks :)
Starting to get a little hang of it! - In reply toraphaelsepulveda⬆:TThomas Gloor @Thomas_Gloor
@raphaelsepulveda
I have an exception question that is related to my template. It would have to do with the naming of ONE print track and it's groupingIs there a way to “tell the script” that
If the track with send is called "EXCEPTION TRACK NAME" call print track "USER DEFINED TRACK-PRINT" and NOT GROUP IT.
Something like this, located where you wrote the exception for the "245 PDLY" and "245" print?
if (trackName === "EXCEPTION TRACK NAME") printTrackName = "USER DEFINED TRACK" + "-" + sendName + " PRINT";
and place your
groupPrintTracks
function in an if/else statement like so? I just don't know how to tell the script to DO NOTHING in the else.if (trackname !== "EXCEPTION TRACK NAME" ) { groupPrintTracks({trackName}); } else { //I don't really know what to write here. Would that work? //void(0)? throw 0? }
Any idea?
Raphael Sepulveda @raphaelsepulveda2022-05-13 05:28:51.878Z
I'd approach it by rearranging that section at the top of the forEach loop to accommodate for more possible exceptions, like this:
if (!hardwareFXNames.includes(sendName)) return; let hardware, printTrackName, printBusName; // EXCEPTIONS ////////////////////////////////////////////////////////////////// // If send is "245 PDLY" then setup a "245" print track instead if (sendName === "245 PDLY") sendName = "245"; //////////////////////////////////////////////////////////////////////////////// hardware = hardwareFx[sendName]; printTrackName = trackName + "-" + sendName + " PRINT" printBusName = sendName + " PRINT"; // MORE EXCEPTIONS ///////////////////////////////////////////////////////////// if (trackName === "EXCEPTION TRACK NAME") printTrackName = "USER DEFINED TRACK" + "-" + sendName + " PRINT"; //////////////////////////////////////////////////////////////////////////////// sf.ui.proTools.trackSelectByName({ names: [trackName] });
And yes, to avoid making the group just forgo the else statement altogether, like this:
if (trackname !== "EXCEPTION TRACK NAME" ) { groupPrintTracks({trackName}); }
- TThomas Gloor @Thomas_Gloor
Hi @raphaelsepulveda
Thank you for answering.So if I wanted to add more exceptions and did it like so:
if (!hardwareFXNames.includes(sendName)) return; // EXCEPTIONS ////////////////////////////////////////////////////////////////// // If send is "245 PDLY" then setup a "245" print track instead if (sendName === "245 PDLY") sendName = "245"; //////////////////////////////////////////////////////////////////////////////// var hardware = hardwareFx[sendName]; var printTrackName = trackName + "-" + sendName + " PRINT" var printBusName = sendName + " PRINT"; // MORE EXCEPTIONS ///////////////////////////////////////////////////////////// if (trackName === "LD // SEND") printTrackName = "LEAD" + "-" + sendName + " PRINT"; if (trackName === "LD VERB SEND") printTrackName = "LEAD" + "-" + sendName + " PRINT"; if (trackName === "LD DLY SEND") printTrackName = "LEAD" + "-" + sendName + " PRINT"; //////////////////////////////////////////////////////////////////////////////// sf.ui.proTools.trackSelectByName({ names: [trackName] });
And the if at the end would look like this
if (trackName !== "LD // SEND") { groupPrintTracks({ trackName }); } if (trackName !== "LD VERB SEND") { groupPrintTracks({ trackName }); } if (trackName !== "LD DLY SEND") { groupPrintTracks({ trackName }); } }
It doesn't work with more than one IF, so I tried to have the exceptions in an array, but didn't manage to make it work. Could you help? What I tried is to have this at the top of the session:
const exceptionTrackNames = ["LD // SEND", "LD VERB SEND", "LD DLY SEND"];
and have the if in the end look like that
if (trackName !== exceptionTrackNames) { groupPrintTracks({ trackName }); }
But it doesn't seem to work. Any idea?
If this would work I could à else that will give the exception tracks PRINT tracks a special treatment.
- In reply toThomas_Gloor⬆:TThomas Gloor @Thomas_Gloor
I managed to find a solution, but don't know if it is really elegant, as I might have more exceptions!
// Creates a Mix/Edit group with the track containing the sends and it's respective Fx Print tracks if (trackName !== "LD // SEND" && trackName !== "LD VERB SEND" && trackName !== "LD DLY SEND") { groupPrintTracks({ trackName }); }
Raphael Sepulveda @raphaelsepulveda2022-05-13 19:14:04.785Z
You were almost there with the array approach! Check this out:
if (!exceptionTrackNames.includes(trackName)) { groupPrintTracks({ trackName }); }
- TThomas Gloor @Thomas_Gloor
aaaaaaaah! Thank you @raphaelsepulveda
From what I understand, I didn't know how to look for a specific sring in an array, am I right?Thank you for fixing it!
I'm now working on getting the prints from the exception tracks to be moved to a specific set of routing folders. I'll post when done!
- TThomas Gloor @Thomas_Gloor
@raphaelsepulveda
I hope you're doing fine!I wanted to know something regarding the exceptions. Would it be possible that the
exceptionTrackNames
array contains a specific set of tracks, but also tracks that include a certain name?It is still about the lead vocals. When there are more that one main vocalist in a song (a feature), I'll normally name tracks that are in the exception array by adding the initial of the artists name and a separator before.
For example:
1 artist song: LEAD // SEND, LEAD VERB SEND, LEAD DLY SEND
2 artists song: H-LEAD // SEND, H-LEAD VERB SEND, H-LEAD DLY SEND, W-LEAD // SEND, and so on...
is there a way to make that work? It has something to do with wildcards, am I right?
Raphael Sepulveda @raphaelsepulveda2022-05-17 00:15:49.822Z
Hey @Thomas_Gloor!
Yeah! For wildcards, I like using this function Christian wrote a while ago.
Put this at the top of the script, where all the other functions are.// Source: https://forum.soundflow.org/-1012#post-7 function matchWildcard(str, rule) { const escapeSpecialRegexCharacters = (str) => str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); const regEx = new RegExp("^" + rule.split("*").map(escapeSpecialRegexCharacters).join(".*") + "$", "i"); return regEx.test(str); }
Basically, you provide this function with a string and a rule—which is a string containing the wildcards—which is an asterisk ( * )—and it will return a boolean based on it finding a match. It's easier to understand with an example, which is coming up!
To implement this in this script, let's first create another array to house the wildcards. Continuing your vocal example...
const wildcardExceptions = ["*LEAD* SEND"];
That first string in the array is a wildcard pattern that will match a string that starts (or not) with something, followed by "LEAD", followed (or not) by something else, and then ends with " SEND". When I say "something", it can be a word, a combination of words, numbers... anything really. This particular pattern will match all the possibilities you mentioned above.
Now replace this:
if (!exceptionTrackNames.includes(trackName)) { groupPrintTracks({ trackName }); }
...with this:
const isGroupException = function () { return exceptionTrackNames.includes(trackName) || wildcardExceptions.some(we => matchWildcard(trackName, we)) }(); if (!isGroupException) { groupPrintTracks({ trackName }); }
...and now any tracks that satisfy the wildcard pattern will be excluded from grouping.
I didn't get a chance to test this, but I'm sure you'll let me know if it doesn't work, haha!
- TThomas Gloor @Thomas_Gloor
Hey @raphaelsepulveda thank you so much for explaining. I'm really trying not to bother too much, but hey, I'm eager to learn and I'm trying to write stuff bigger than my current understanding of things. So I can't stress how much your help is valuable.
Thank you for this! I'll try asap!
- In reply toraphaelsepulveda⬆:TThomas Gloor @Thomas_Gloor
ehem ehem @raphaelsepulveda
me again. sorry. you called it.
it doesn't seem to work. I tried it with a track called H-LEAD VERB SEND.I'm trying to understand why, but really cant. any input?
Raphael Sepulveda @raphaelsepulveda2022-05-17 21:31:52.440Z
Haha, I was afraid something might go wrong 🤣
Fortunately, the code did work on my tests, so it might be a matter of making sure it's placed correctly. I updated the main script above, give it another shot!
- TThomas Gloor @Thomas_Gloor
@raphaelsepulveda
SO. It drove me nuts for a minute, as I had placed everything where it should (it doesn't show much, but I'm actually doing my homework haha).
After 30 minutes, I just realised that you named the wildcard exceptions*LEAD* SEND
and for it to work in my session it should name*LD* SEND
Stupid me.Thank you very much! It will come in handy, as I'm currently mixing a song with 4 different singers and a pretty hardcore group arcitechture.
Raphael Sepulveda @raphaelsepulveda2022-05-18 17:10:14.503Z
Haha, pesky little details. Glad it's working!
- TThomas Gloor @Thomas_Gloor
Hey @raphaelsepulveda . I hope you're doing fine :)
One question. I'm trying to use parts of your code to inactivate the hardware sends once printed. But I can't find a way to inactivate (not mute, because of automations) by name.
Do you have any idea?
Thanks!