Using Colors of Tracks to Define Actions
Hi Geniuses
I have a bunch of scripts I run to prep a session. I run one script for each of my food groups (bass, guitars, drums, BVs, LVs, Keys, Etc)
Before I run each of the food group scripts, i need to select the tracks in that food group.
The thing is, the tracks in the food groups always have the exact same color (which i set with a soundflow command).
So, in theory, I should be able to automate all those individual food group prep scripts by saying something like:
"find all tracks with Red 3 color, then run "Bass Prep Script" > next find all tracks with Green 4 color, then run "Keys Prep Script, etc etc etc"
problem is i don't know how to get this functionality into my scripts. is this possible?
If so, can someone point me towards a way to select tracks based off color of track?
Linked from:
- Chad Wahlbrink @Chad2023-08-03 14:12:56.240Z
Hey @Philip_weinrobe!
To my knowledge, the color of a track in Pro Tools is not directly accessible. I believe the only way to do what you are asking would be to utilize @Andrew_Scheps' Scheps Color Deck, which is a "premium app" on the SoundFlow store. This app allows you to select tracks based on color, with the caveat that the tracks must be visible on the screen to register them.
For more information on that app's functionality, check out the demo video here (I've cued up 4:21, where Andrew demonstrates the "select tracks by color" function) :
https://youtu.be/a8dwa2A39Vo?t=259 - In reply toPhilip_weinrobe⬆:Andrew Scheps @Andrew_Scheps
Hi @Philip_weinrobe ,
As @chadwahlbrink says, there is no way to do this directly other than my fabulous app. Ha! Can I suggest another way to approach it? Add all of the members of each food group to a group (which you can disable if you want). That way you can select all of the members of the group by clicking the dot to the left of the group name. If you use consistent group names then you can search for the name in the group list and then click the dot. As to how to code that, it's a bit tricky but not insanely so and I'm sure the forums will help you get there!
Hope that helps!
AndrewChad Wahlbrink @Chad2023-08-03 15:40:40.789Z
@Philip_weinrobe, Andrew's suggestion is a great alternative approach.
You could take this idea further by having your scripts that color tracks add those tracks to specified groups.
Here's an example of an implementation for selecting a specified group you could use. You can swap "ALL DRUMS" at the start of this for whichever group you'd like to prep.
let groupNameToSelect = "ALL DRUMS"; ///////////////////////////////////////////////////////////////////////////////////////////////////// function main() { // Collect session group information let groups = getTrackGroups(); // Showing all tracks to make sure the tracks in the group are ready to be processed sf.ui.proTools.mainWindow.trackListPopupButton.popupMenuSelect({ menuPath: ["Show All Tracks"] }); // Filter for the desired group groups.filter(name => name.name === groupNameToSelect)[0].groupSelectionBtn.elementClick(); let selectedTracks = sf.ui.proTools.selectedTracks; // Ensure those tracks are visible so you can start processing them. ensureAnAudioTrackIsSelectedAndVisible(selectedTracks); // Uncomment if you want to restore previously shown tracks // sf.ui.proTools.mainWindow.trackListPopupButton.popupMenuSelect({ menuPath: ["Restore Previously Shown Tracks"] }); } main(); ///////////////////////////////////////////////////////////////////////////////////////////////////// // Function - Get Track Group Props function getTrackGroups() { // Declare Group List const groupList = sf.ui.proTools.mainWindow.tables.whoseTitle.is("Group List").first; const groupRows = groupList.childrenByRole("AXRow"); sf.ui.proTools.groupsEnsureGroupListIsVisible(); // Map Groups const groupsList = groupRows.allItems.map(group => { const groupSelectionBtn = group.childrenByRole('AXCell').first.buttons.first; const groupTitleBtnName = group.childrenByRole('AXCell').allItems[1].buttons.first.value.invalidate().value; return { name: groupTitleBtnName.split(' - ')[1], groupID: groupTitleBtnName.split(' - ')[0].split(' ').slice(-1)[0], groupSelectionBtn: groupSelectionBtn, } }); return groupsList; }; /// Borrowing from Raphael Sepulveda https://forum.soundflow.org/-6034#post-9 to make sure tracks are in view /** * @param {object} arg * @param {AxPtTrackHeader[]} [arg.tracks] - Optional. If provided gets top most from those tracks. */ function determineTopMostEditTrack({ tracks } = {}) { function getTopMostTrack(tracks) { return tracks.filter(h => h.frame.y >= editTimeLineTopY)[0]; } sf.ui.proTools.mainWindow.invalidate(); const editTimeLineTopY = sf.ui.proTools.mainWindow.timelineFocusButton.frame.y; return getTopMostTrack(tracks || sf.ui.proTools.visibleTrackHeaders); }; /** @param {AxPtTrackHeader} track */ function isAudioTrack(track) { return track.title.value.endsWith('Audio Track '); } function ensureAnAudioTrackIsSelectedAndVisible(audioTracks) { if (!sf.ui.proTools.selectedTrackCount || !isAudioTrack(sf.ui.proTools.selectedTrack)) { determineTopMostEditTrack({ tracks: audioTracks }).trackScrollToView(); } else { sf.ui.proTools.selectedTrack.trackScrollToView(); } }
Chad Wahlbrink @Chad2023-08-03 15:42:56.822Z
Shout out to @raphaelsepulveda's brilliant code for the last couple of functions I'm using to ensure the tracks are in view after selection
- PPhilip weinrobe @Philip_weinrobe
this is a great idea and one i was thinking of...since i'm already selecting tracks to color them.
weirdly when i run this script it doesn't do anything. for my workflow, i'd be looking for something that functioned like this:
-
I select a track(s)
-
I fire off a script that
A: colors tracks a specific color
B: adds track to a specific group (if group doesn't exist, creates that group) -
Once I am done coloring (and therefore grouping) the tracks I fire off a new script that
A: selects tracks in an already created and populated specific group
B: runs an existing script I already have that's working great to do all the other prep things I do.
If I could get this working, I could build the big loop to make it do a whole song at once!
Any ideas?
I feel like this isn't that hard. I don't code myself, but can maybe try cobbling this together through functions and scripts I already have laying about :)will report back!
PhilipChris Shaw @Chris_Shaw2023-08-03 18:20:34.226Z
You can all of this with track presets: track presets can set colors and groups. (If the group doesn’t exist it will create it). Make a preset for each food group. You can then make a deck with a with a button for each track type/food group.
This is how I prep my sessions.- In reply toPhilip_weinrobe⬆:
Chad Wahlbrink @Chad2023-08-03 19:18:09.639Z
Hey @Philip_weinrobe!
Note that the code from this post: Using Colors of Tracks to Define Actions #post-4 will only run for you if you have a group in your group list called "ALL DRUMS."
I meant it to be a "template" for selecting a named group. You can swap the "ALL DRUMS" in the first line for whatever group you are trying to select for prepping.let groupNameToSelect = "ALL DRUMS";
@Chris_Shaw 's suggestion is great for this as well. Recalling a track preset for each "food group" can set the color and group in one go for all selected tracks.Chad Wahlbrink @Chad2023-08-03 19:24:03.619Z
@Philip_weinrobe, you could use the built-in SoundFlow Template Command under "Track Functions" in the official Pro Tools Package for recalling track presets. This would allow you to build a deck similar to Chris' idea.
Alternatively, you could try the command in my CW Pro Tools Utilitiespackage for loading track presets for selected tracks by search.
Chad Wahlbrink @Chad2023-08-03 20:11:08.545Z2023-08-17 17:50:38.945Z
@Philip_weinrobe This is an example of how you could string things together.
Drop the names of your foodgroup groups into the array in line 3:// Put All Names of Groups to Prep in the array below // (in the square brackets, in quotations marks, with commas separating) let groupsToPrep = ["BASS PREP", "DRUMS PREP", "PERC PREP", "WINDS PREP", "STRINGS PREP", "KEYS PREP", "GTR PREP", "BV PREP", "LV PREP"]; let groups = getTrackGroups(), groupNames = groups.map(x => x.name), groupsInSessionToPrep = groupsToPrep.filter(item => groupNames.includes(item)), numberOfGroups = groupsInSessionToPrep.length, currentView = saveCurrentTrackView(); if (numberOfGroups == 0) { throw "No Matching Groups Were Found In Session"; } try { // For Loop to Repeat Setup action for each group for (let i = 0; i < numberOfGroups; i++) { // try to select each group try { // ↓↓↓ PREP SCRIPT HERE if (groupsInSessionToPrep[i] == "BASS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); // sf.soundflow.runCommand({ commandId: ""user:xxxxxxxxxxxx:xxxxxxxxxxxx" }); sf.ui.proTools.colorsSelect({ colorNumber: 1, colorBrightness: "Dark", }); } if (groupsInSessionToPrep[i] == "DRUMS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); // sf.soundflow.runCommand({ commandId: ""user:xxxxxxxxxxxx:xxxxxxxxxxxx" }); sf.ui.proTools.colorsSelect({ colorNumber: 1, colorBrightness: "Light", }); } if (groupsInSessionToPrep[i] == "ALL BASS") { selectTracksInGroup(groupsInSessionToPrep[i]); // sf.soundflow.runCommand({ commandId: ""user:xxxxxxxxxxxx:xxxxxxxxxxxx" }); sf.ui.proTools.colorsSelect({ colorNumber: 10, colorBrightness: "Medium", }); } //↑↑↑ PREP SCRIPT HERE ↑↑↑ } catch (err) { throw err; } } } catch (err) { throw err; } finally { recallAndDeleteTrackView(currentView); } ///////////////////////////////////////////////////////////////////////////////////////////////////// // Function - Get Track Group Props function getTrackGroups() { // Declare Group List const groupList = sf.ui.proTools.mainWindow.tables.whoseTitle.is("Group List").first; const groupRows = groupList.childrenByRole("AXRow"); sf.ui.proTools.groupsEnsureGroupListIsVisible(); // Map Groups const groupsList = groupRows.allItems.map(group => { const groupSelectionBtn = group.childrenByRole('AXCell').first.buttons.first; const groupTitleBtnName = group.childrenByRole('AXCell').allItems[1].buttons.first.value.invalidate().value; return { name: groupTitleBtnName.split(' - ')[1], groupID: groupTitleBtnName.split(' - ')[0].split(' ').slice(-1)[0], groupSelectionBtn: groupSelectionBtn, } }); return groupsList; }; ///////////////////////////////////////////////////////////////////////////////////////////////////// function selectTracksInGroup(groupname) { // Collect session group information let groups = getTrackGroups(); // Showing all tracks to make sure the tracks in the group are ready to be processed sf.ui.proTools.mainWindow.trackListPopupButton.popupMenuSelect({ menuPath: ["Show All Tracks"] }); // Filter for the desired group groups.filter(name => name.name === groupname)[0].groupSelectionBtn.elementClick(); let selectedTracks = sf.ui.proTools.selectedTracks; // Ensure those tracks are visible so you can start processing them. ensureAnAudioTrackIsSelectedAndVisible(selectedTracks); // Uncomment if you want to restore previously shown tracks // sf.ui.proTools.mainWindow.trackListPopupButton.popupMenuSelect({ menuPath: ["Restore Previously Shown Tracks"] }); } /// Borrowing from Raphael Sepulveda https://forum.soundflow.org/-6034#post-9 to make sure tracks are in view /** * @param {object} arg * @param {AxPtTrackHeader[]} [arg.tracks] - Optional. If provided gets top most from those tracks. */ function determineTopMostEditTrack({ tracks } = {}) { function getTopMostTrack(tracks) { return tracks.filter(h => h.frame.y >= editTimeLineTopY)[0]; } sf.ui.proTools.mainWindow.invalidate(); const editTimeLineTopY = sf.ui.proTools.mainWindow.timelineFocusButton.frame.y; return getTopMostTrack(tracks || sf.ui.proTools.visibleTrackHeaders); }; /** @param {AxPtTrackHeader} track */ function isAudioTrack(track) { return track.title.value.endsWith('Audio Track '); } function ensureAnAudioTrackIsSelectedAndVisible(audioTracks) { if (!sf.ui.proTools.selectedTrackCount || !isAudioTrack(sf.ui.proTools.selectedTrack)) { determineTopMostEditTrack({ tracks: audioTracks }).trackScrollToView(); } else { sf.ui.proTools.selectedTrack.trackScrollToView(); } } /// Borrowing from Andrew Scheps https://soundflow.org/store/pkg/screen-layout-helper-functions to make sure tracks are in view function saveCurrentTrackView() { sf.ui.proTools.appActivateMainWindow(); let memLocWinExists = sf.ui.proTools.memoryLocationsWindow.exists; // create a new memory location sf.keyboard.press({ keys: "numpad enter", }); sf.ui.proTools.newMemoryLocationDialog.elementWaitFor(); //Wait for the Mem Location dialog to appear and assign it to the memLocDlg variable var memLocDlg = sf.ui.proTools.dialogWaitForManual({ dialogTitle: 'New Memory Location' }).dialog; // We only want to store the track visibilty so clear all check boxes and then check the appropriate one var checkBoxes = memLocDlg.getElements("AXChildren").filter(function (e) { return e.fullRole == "AXCheckBox" }); for (var i = 0; i < 6; i++) { checkBoxes[i].checkboxSet({ targetValue: "Disable", }); } checkBoxes[2].checkboxSet({ targetValue: "Enable", }); checkBoxes[3].checkboxSet({ targetValue: "Enable", }); //Name the Location Track Visibilty sf.ui.proTools.newMemoryLocationDialog.textFields.allItems[0].elementSetTextFieldWithAreaValue({ value: "Track Visibility", }); //Get the Location Number and pass it back so we can delete it when we're done var locationNumber = sf.ui.proTools.newMemoryLocationDialog.textFields.allItems[1].value.value; // Set time Properties to none sf.ui.proTools.newMemoryLocationDialog.radioButtons.allItems[2].elementClick(); //Create the Location sf.ui.proTools.newMemoryLocationDialog.buttons.whoseTitle.is('OK').first.elementClick(); sf.ui.proTools.newMemoryLocationDialog.elementWaitFor({ waitType: "Disappear", }); return { locationNumber, memLocWinExists }; } function recallAndDeleteTrackView(lastView) { sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: Number(lastView.locationNumber), }); sf.ui.proTools.memoryLocationsShowWindow(); sf.ui.proTools.memoryLocationsWindow.popupButtons.first.popupMenuSelect({ menuSelector: items => items.filter(i => i.names[0].match(/^Clear \"/))[0] }); if (!lastView.memLocWinExists) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Memory Locations"], targetValue: 'Disable' //or 'Enable' or 'Toggle' }); sf.ui.proTools.memoryLocationsWindow.elementWaitFor({ waitType: "Disappear", }); } }
Chad Wahlbrink @Chad2023-08-03 20:13:20.025Z
@Philip_weinrobe finally, here's a video demo of how to set it all up:
https://www.dropbox.com/s/mhnw79vvtcm56l3/2023-08-03 session prepper.mp4?dl=0Chad Wahlbrink @Chad2023-08-03 20:29:05.749Z
One final note on this... In the video, I mentioned that you'd need the group to be present in the session for this to work. That'd be super fiddly, so I changed that just now.
I tweaked the script so that you can list all of your "food group" track groups.
Now, the script tries to select each group and continues to the next one if the current group doesn't exist. That way, you could build this out to do everything you want and then have a button to prep all the groups in one go.
It's also possible that just setting up robust track presets is enough to get you the functionality you want. However, this more automated approach could be handy if you are trying to move tracks to specific folders or places in the session.- PPhilip weinrobe @Philip_weinrobe
hi all!
this is working great and i'm building it out.
the only issue is it relies on me having all these track presets on my computer for the scripts to go find.
when i am at other studios, this will fail.
how do I make a script that creates the presets on the fly? i looked in the store for a tool like this.
i know a lot of @Chris_Shaw scripts do this but couldn't find a tool to build my own from a template/preset system.any advice here appreciated!
philip
Chad Wahlbrink @Chad2023-08-17 15:21:31.481Z
Would backing up the "Track Presets" folder from "~/Documents/Pro Tools/Track Presets" to an external hard drive be a valid option for this? Then you could just pop a folder of presets onto the studio computer and delete them at the end of the session if necessary.
Building the presets on the fly would take a good amount of work since they may use "custom I/O" for sends, etc.
- In reply toChad⬆:PPhilip weinrobe @Philip_weinrobe
Hi Chad!
i got the script working with presets but actually having trouble inserting my own "prep script" in now.
i have a huge macro i am trying to call.
Can i call it with a command ID?
How do I do that?
for example, I want ALL BASS to run a specific command ID in the place where you now have it simply changing the color.Thank you! I tried, but failed.
Chad Wahlbrink @Chad2023-08-17 15:43:43.011Z2023-08-17 18:14:10.639Z
Hey @Philip_weinrobe.
You would need to usesf.sfoundflow.runcommand({})
to run a separate macro for each group. So the structure would look like this:// ↓↓↓ PREP SCRIPT HERE if (groupsInSessionToPrep[i] == "ALL DRUMS") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:xxxxxxxxxxxx:xxxxxxxxxxxx" }); } if (groupsInSessionToPrep[i] == "ALL PERC") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:xxxxxxxxxxxx:xxxxxxxxxxxx" }); } if (groupsInSessionToPrep[i] == "ALL BASS") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:xxxxxxxxxxxx:xxxxxxxxxxxx" }); } //↑↑↑ PREP SCRIPT HERE ↑↑↑
To get the command ID of the macros you'd like to run for each group, you can select the macro in SoundFlow, and click the menu item "Command" -> "Copy Command ID." This should copy a bit of text in the format of "user:xxxxxxxxxxxx:xxxxxxxxxxxx" to your clipboard to insert in each runCommand action.
Let me know if that works for your scenario.- PPhilip weinrobe @Philip_weinrobe
this is working!
only issue now is that somehwere in your script it is causing my hidden and inactive tracks to become visible.
anyways to change that?- In reply toPhilip_weinrobe⬆:
Chad Wahlbrink @Chad2023-08-17 16:44:05.660Z
I updated the original script to restore your tracks shown at the end of the script.
Note: I also tweaked the script to only run on groups that are present in the session. So it will only run 3 times if it only finds 3 of the possible groups. Previously, it would try to run for each group in line 3
- PPhilip weinrobe @Philip_weinrobe
hi! amazing
just updated to the newest script but now it just follows the order in the array regardless of group name.
for example, my session had no "BASS" group but since "BASS" was first in my array it grabbed my actual first group (which was WINDS) and prepped it using the "BASS" commandID.The last version of the script didn't do this. However, the last version did not skip a group if it wasn't present. Which is important since my array is exhaustive but each session usually doesn't have each "prep group" represented.
So close!
btw: here's where my script is at. maybe you could see if i did something wrong when inputting my variables?
i copied some over from an earlier version so i didn't have to type it all again? maybe i did that wrong- In reply toPhilip_weinrobe⬆:PPhilip weinrobe @Philip_weinrobe
// Put All Names of Groups to Prep in the array below // (in the square brackets, in quotations marks, with commas separating) let groupsToPrep = ["BASS PREP", "DRUMS PREP", "PERC PREP", "WINDS PREP", "STRINGS PREP", "KEYS PREP", "GTR PREP", "BV PREP", "LV PREP"]; let groups = getTrackGroups(), groupNames = groups.map(x => x.name), groupsInSessionToPrep = groupsToPrep.filter(item => groupNames.includes(item)), numberOfGroups = groupsInSessionToPrep.length, currentView = saveCurrentTrackView(); if (numberOfGroups == 0) { throw "No Matching Groups Were Found In Session"; } try { // For Loop to Repeat Setup action for each group for (let i = 0; i < numberOfGroups; i++) { // try to select each group try { selectTracksInGroup(groupsInSessionToPrep[i]); // ↓↓↓ PREP SCRIPT HERE if (groupsToPrep[i] == "BASS PREP") { sf.soundflow.runCommand({ commandId: "user:default:ckurl84zv000j5a10hpxrowb1" }); } if (groupsToPrep[i] == "DRUMS PREP") { sf.soundflow.runCommand({ commandId: "user:default:ckurl9hqm000k5a100o6u5vd2" }); } if (groupsToPrep[i] == "PERC PREP") { sf.soundflow.runCommand({ commandId: "user:default:ckurdj4ei000f5a107oskemu9" }); } if (groupsToPrep[i] == "WINDS PREP") { sf.soundflow.runCommand({ commandId: "user:default:ckyj8fl9w000pzw10al04orr2" }); } if (groupsToPrep[i] == "STRINGS PREP") { sf.soundflow.runCommand({ commandId: "user:default:ckyj8i1r1000rzw10g1az86rq" }); } if (groupsToPrep[i] == "KEYS PREP") { sf.soundflow.runCommand({ commandId: "user:default:ckurd9xkj00095a103lxmcvm5" }); } if (groupsToPrep[i] == "GTR PREP") { sf.soundflow.runCommand({ commandId: "user:default:ckurdc58j000b5a10m0hivul1" }); } if (groupsToPrep[i] == "BV PREP") { sf.soundflow.runCommand({ commandId: "user:default:ckurldlpx000n5a10nyehvf8v" }); } if (groupsToPrep[i] == "LV PREP") { sf.soundflow.runCommand({ commandId: "user:default:ckurdeln9000d5a10fx2wzso7" }); } //↑↑↑ PREP SCRIPT HERE ↑↑↑ } catch (err) { throw err; } } } catch (err) { throw err; } finally { recallAndDeleteTrackView(currentView); } ///////////////////////////////////////////////////////////////////////////////////////////////////// // Function - Get Track Group Props function getTrackGroups() { // Declare Group List const groupList = sf.ui.proTools.mainWindow.tables.whoseTitle.is("Group List").first; const groupRows = groupList.childrenByRole("AXRow"); sf.ui.proTools.groupsEnsureGroupListIsVisible(); // Map Groups const groupsList = groupRows.allItems.map(group => { const groupSelectionBtn = group.childrenByRole('AXCell').first.buttons.first; const groupTitleBtnName = group.childrenByRole('AXCell').allItems[1].buttons.first.value.invalidate().value; return { name: groupTitleBtnName.split(' - ')[1], groupID: groupTitleBtnName.split(' - ')[0].split(' ').slice(-1)[0], groupSelectionBtn: groupSelectionBtn, } }); return groupsList; }; ///////////////////////////////////////////////////////////////////////////////////////////////////// function selectTracksInGroup(groupname) { // Collect session group information let groups = getTrackGroups(); // Showing all tracks to make sure the tracks in the group are ready to be processed sf.ui.proTools.mainWindow.trackListPopupButton.popupMenuSelect({ menuPath: ["Show All Tracks"] }); // Filter for the desired group groups.filter(name => name.name === groupname)[0].groupSelectionBtn.elementClick(); let selectedTracks = sf.ui.proTools.selectedTracks; // Ensure those tracks are visible so you can start processing them. ensureAnAudioTrackIsSelectedAndVisible(selectedTracks); // Uncomment if you want to restore previously shown tracks // sf.ui.proTools.mainWindow.trackListPopupButton.popupMenuSelect({ menuPath: ["Restore Previously Shown Tracks"] }); } /// Borrowing from Raphael Sepulveda https://forum.soundflow.org/-6034#post-9 to make sure tracks are in view /** * @param {object} arg * @param {AxPtTrackHeader[]} [arg.tracks] - Optional. If provided gets top most from those tracks. */ function determineTopMostEditTrack({ tracks } = {}) { function getTopMostTrack(tracks) { return tracks.filter(h => h.frame.y >= editTimeLineTopY)[0]; } sf.ui.proTools.mainWindow.invalidate(); const editTimeLineTopY = sf.ui.proTools.mainWindow.timelineFocusButton.frame.y; return getTopMostTrack(tracks || sf.ui.proTools.visibleTrackHeaders); }; /** @param {AxPtTrackHeader} track */ function isAudioTrack(track) { return track.title.value.endsWith('Audio Track '); } function ensureAnAudioTrackIsSelectedAndVisible(audioTracks) { if (!sf.ui.proTools.selectedTrackCount || !isAudioTrack(sf.ui.proTools.selectedTrack)) { determineTopMostEditTrack({ tracks: audioTracks }).trackScrollToView(); } else { sf.ui.proTools.selectedTrack.trackScrollToView(); } } /// Borrowing from Andrew Scheps https://soundflow.org/store/pkg/screen-layout-helper-functions to make sure tracks are in view function saveCurrentTrackView() { sf.ui.proTools.appActivateMainWindow(); let memLocWinExists = sf.ui.proTools.memoryLocationsWindow.exists; // create a new memory location sf.keyboard.press({ keys: "numpad enter", }); sf.ui.proTools.newMemoryLocationDialog.elementWaitFor(); //Wait for the Mem Location dialog to appear and assign it to the memLocDlg variable var memLocDlg = sf.ui.proTools.dialogWaitForManual({ dialogTitle: 'New Memory Location' }).dialog; // We only want to store the track visibilty so clear all check boxes and then check the appropriate one var checkBoxes = memLocDlg.getElements("AXChildren").filter(function (e) { return e.fullRole == "AXCheckBox" }); for (var i = 0; i < 6; i++) { checkBoxes[i].checkboxSet({ targetValue: "Disable", }); } checkBoxes[2].checkboxSet({ targetValue: "Enable", }); checkBoxes[3].checkboxSet({ targetValue: "Enable", }); //Name the Location Track Visibilty sf.ui.proTools.newMemoryLocationDialog.textFields.allItems[0].elementSetTextFieldWithAreaValue({ value: "Track Visibility", }); //Get the Location Number and pass it back so we can delete it when we're done var locationNumber = sf.ui.proTools.newMemoryLocationDialog.textFields.allItems[1].value.value; // Set time Properties to none sf.ui.proTools.newMemoryLocationDialog.radioButtons.allItems[2].elementClick(); //Create the Location sf.ui.proTools.newMemoryLocationDialog.buttons.whoseTitle.is('OK').first.elementClick(); sf.ui.proTools.newMemoryLocationDialog.elementWaitFor({ waitType: "Disappear", }); return { locationNumber, memLocWinExists }; } function recallAndDeleteTrackView(lastView) { sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: Number(lastView.locationNumber), }); sf.ui.proTools.memoryLocationsShowWindow(); sf.ui.proTools.memoryLocationsWindow.popupButtons.first.popupMenuSelect({ menuSelector: items => items.filter(i => i.names[0].match(/^Clear \"/))[0] }); if (!lastView.memLocWinExists) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Memory Locations"], targetValue: 'Disable' //or 'Enable' or 'Toggle' }); sf.ui.proTools.memoryLocationsWindow.elementWaitFor({ waitType: "Disappear", }); } }
Chad Wahlbrink @Chad2023-08-17 17:49:09.124Z
Hey @Philip_weinrobe
Give this a shot:// Put All Names of Groups to Prep in the array below // (in the square brackets, in quotations marks, with commas separating) let groupsToPrep = ["BASS PREP", "DRUMS PREP", "PERC PREP", "WINDS PREP", "STRINGS PREP", "KEYS PREP", "GTR PREP", "BV PREP", "LV PREP"]; let groups = getTrackGroups(), groupNames = groups.map(x => x.name), groupsInSessionToPrep = groupsToPrep.filter(item => groupNames.includes(item)), numberOfGroups = groupsInSessionToPrep.length, currentView = saveCurrentTrackView(); if (numberOfGroups == 0) { throw "No Matching Groups Were Found In Session"; } try { // For Loop to Repeat Setup action for each group for (let i = 0; i < numberOfGroups; i++) { // try to select each group try { // ↓↓↓ PREP SCRIPT HERE if (groupsInSessionToPrep[i] == "BASS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurl84zv000j5a10hpxrowb1" }); } if (groupsInSessionToPrep[i] == "DRUMS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurl9hqm000k5a100o6u5vd2" }); } if (groupsInSessionToPrep[i] == "PERC PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurdj4ei000f5a107oskemu9" }); } if (groupsInSessionToPrep[i] == "WINDS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckyj8fl9w000pzw10al04orr2" }); } if (groupsInSessionToPrep[i] == "STRINGS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckyj8i1r1000rzw10g1az86rq" }); } if (groupsInSessionToPrep[i] == "KEYS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurd9xkj00095a103lxmcvm5" }); } if (groupsInSessionToPrep[i] == "GTR PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurdc58j000b5a10m0hivul1" }); } if (groupsInSessionToPrep[i] == "BV PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurldlpx000n5a10nyehvf8v" }); } if (groupsInSessionToPrep[i] == "LV PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurdeln9000d5a10fx2wzso7" }); } //↑↑↑ PREP SCRIPT HERE ↑↑↑ } catch (err) { throw err; } } } catch (err) { throw err; } finally { recallAndDeleteTrackView(currentView); } ///////////////////////////////////////////////////////////////////////////////////////////////////// // Function - Get Track Group Props function getTrackGroups() { // Declare Group List const groupList = sf.ui.proTools.mainWindow.tables.whoseTitle.is("Group List").first; const groupRows = groupList.childrenByRole("AXRow"); sf.ui.proTools.groupsEnsureGroupListIsVisible(); // Map Groups const groupsList = groupRows.allItems.map(group => { const groupSelectionBtn = group.childrenByRole('AXCell').first.buttons.first; const groupTitleBtnName = group.childrenByRole('AXCell').allItems[1].buttons.first.value.invalidate().value; return { name: groupTitleBtnName.split(' - ')[1], groupID: groupTitleBtnName.split(' - ')[0].split(' ').slice(-1)[0], groupSelectionBtn: groupSelectionBtn, } }); return groupsList; }; ///////////////////////////////////////////////////////////////////////////////////////////////////// function selectTracksInGroup(groupname) { // Collect session group information let groups = getTrackGroups(); // Showing all tracks to make sure the tracks in the group are ready to be processed sf.ui.proTools.mainWindow.trackListPopupButton.popupMenuSelect({ menuPath: ["Show All Tracks"] }); // Filter for the desired group groups.filter(name => name.name === groupname)[0].groupSelectionBtn.elementClick(); let selectedTracks = sf.ui.proTools.selectedTracks; // Ensure those tracks are visible so you can start processing them. ensureAnAudioTrackIsSelectedAndVisible(selectedTracks); // Uncomment if you want to restore previously shown tracks // sf.ui.proTools.mainWindow.trackListPopupButton.popupMenuSelect({ menuPath: ["Restore Previously Shown Tracks"] }); } /// Borrowing from Raphael Sepulveda https://forum.soundflow.org/-6034#post-9 to make sure tracks are in view /** * @param {object} arg * @param {AxPtTrackHeader[]} [arg.tracks] - Optional. If provided gets top most from those tracks. */ function determineTopMostEditTrack({ tracks } = {}) { function getTopMostTrack(tracks) { return tracks.filter(h => h.frame.y >= editTimeLineTopY)[0]; } sf.ui.proTools.mainWindow.invalidate(); const editTimeLineTopY = sf.ui.proTools.mainWindow.timelineFocusButton.frame.y; return getTopMostTrack(tracks || sf.ui.proTools.visibleTrackHeaders); }; /** @param {AxPtTrackHeader} track */ function isAudioTrack(track) { return track.title.value.endsWith('Audio Track '); } function ensureAnAudioTrackIsSelectedAndVisible(audioTracks) { if (!sf.ui.proTools.selectedTrackCount || !isAudioTrack(sf.ui.proTools.selectedTrack)) { determineTopMostEditTrack({ tracks: audioTracks }).trackScrollToView(); } else { sf.ui.proTools.selectedTrack.trackScrollToView(); } } /// Borrowing from Andrew Scheps https://soundflow.org/store/pkg/screen-layout-helper-functions to make sure tracks are in view function saveCurrentTrackView() { sf.ui.proTools.appActivateMainWindow(); let memLocWinExists = sf.ui.proTools.memoryLocationsWindow.exists; // create a new memory location sf.keyboard.press({ keys: "numpad enter", }); sf.ui.proTools.newMemoryLocationDialog.elementWaitFor(); //Wait for the Mem Location dialog to appear and assign it to the memLocDlg variable var memLocDlg = sf.ui.proTools.dialogWaitForManual({ dialogTitle: 'New Memory Location' }).dialog; // We only want to store the track visibilty so clear all check boxes and then check the appropriate one var checkBoxes = memLocDlg.getElements("AXChildren").filter(function (e) { return e.fullRole == "AXCheckBox" }); for (var i = 0; i < 6; i++) { checkBoxes[i].checkboxSet({ targetValue: "Disable", }); } checkBoxes[2].checkboxSet({ targetValue: "Enable", }); checkBoxes[3].checkboxSet({ targetValue: "Enable", }); //Name the Location Track Visibilty sf.ui.proTools.newMemoryLocationDialog.textFields.allItems[0].elementSetTextFieldWithAreaValue({ value: "Track Visibility", }); //Get the Location Number and pass it back so we can delete it when we're done var locationNumber = sf.ui.proTools.newMemoryLocationDialog.textFields.allItems[1].value.value; // Set time Properties to none sf.ui.proTools.newMemoryLocationDialog.radioButtons.allItems[2].elementClick(); //Create the Location sf.ui.proTools.newMemoryLocationDialog.buttons.whoseTitle.is('OK').first.elementClick(); sf.ui.proTools.newMemoryLocationDialog.elementWaitFor({ waitType: "Disappear", }); return { locationNumber, memLocWinExists }; } function recallAndDeleteTrackView(lastView) { sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: Number(lastView.locationNumber), }); sf.ui.proTools.memoryLocationsShowWindow(); sf.ui.proTools.memoryLocationsWindow.popupButtons.first.popupMenuSelect({ menuSelector: items => items.filter(i => i.names[0].match(/^Clear \"/))[0] }); if (!lastView.memLocWinExists) { sf.ui.proTools.menuClick({ menuPath: ["Window", "Memory Locations"], targetValue: 'Disable' //or 'Enable' or 'Toggle' }); sf.ui.proTools.memoryLocationsWindow.elementWaitFor({ waitType: "Disappear", }); } }
- In reply toPhilip_weinrobe⬆:
Chad Wahlbrink @Chad2023-08-17 17:58:38.366Z
The issue was in this structure:
if (groupsInSessionToPrep[i] == "BASS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:xxxxxxxxxxxx:xxxxxxxxxxxx" }); }
Previously, the scripted pointed to both the non-filtered list (groupsToPrep) and the filtered list (groupsInSessionToPrep).
- PPhilip weinrobe @Philip_weinrobe
awesome, will try in next 10 minutes and report back :)
- PPhilip weinrobe @Philip_weinrobe
i think we are in business :)
- PPhilip weinrobe @Philip_weinrobe
ok, something keeps crashing when the script is interacting with your track visibility marker.
i think, based on how i use this, it's not necessary.is there a way to remove that whole track visibility functionality?
- PPhilip weinrobe @Philip_weinrobe
the script is failing randomly but each time i check the script line it fails at via the log it's something to do with the track visibility memory location. let's just try commenting all that stuff out. i bet it will work great.
Chad Wahlbrink @Chad2023-08-17 20:27:39.199Z
@Philip_weinrobe, heard! I was adapting that from an older script and didn't thoroughly test it. I had to update it for 2023.6 since the memory locations changed significantly for that release.
Try this one:// Put All Names of Groups to Prep in the array below // (in the square brackets, in quotations marks, with commas separating) let groupsToPrep = ["BASS PREP", "DRUMS PREP", "PERC PREP", "WINDS PREP", "STRINGS PREP", "KEYS PREP", "GTR PREP", "BV PREP", "LV PREP"]; let groups = getTrackGroups(), groupNames = groups.map(x => x.name), groupsInSessionToPrep = groupsToPrep.filter(item => groupNames.includes(item)), numberOfGroups = groupsInSessionToPrep.length if (numberOfGroups == 0) { throw "No Matching Groups Were Found In Session"; } // For Loop to Repeat Setup action for each group for (let i = 0; i < numberOfGroups; i++) { // try to select each group try { // ↓↓↓ PREP SCRIPT HERE if (groupsInSessionToPrep[i] == "BASS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurl84zv000j5a10hpxrowb1" }); } if (groupsInSessionToPrep[i] == "DRUMS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurl9hqm000k5a100o6u5vd2" }); } if (groupsInSessionToPrep[i] == "PERC PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurdj4ei000f5a107oskemu9" }); } if (groupsInSessionToPrep[i] == "WINDS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckyj8fl9w000pzw10al04orr2" }); } if (groupsInSessionToPrep[i] == "STRINGS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckyj8i1r1000rzw10g1az86rq" }); } if (groupsInSessionToPrep[i] == "KEYS PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurd9xkj00095a103lxmcvm5" }); } if (groupsInSessionToPrep[i] == "GTR PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurdc58j000b5a10m0hivul1" }); } if (groupsInSessionToPrep[i] == "BV PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurldlpx000n5a10nyehvf8v" }); } if (groupsInSessionToPrep[i] == "LV PREP") { selectTracksInGroup(groupsInSessionToPrep[i]); sf.soundflow.runCommand({ commandId: "user:default:ckurdeln9000d5a10fx2wzso7" }); } //↑↑↑ PREP SCRIPT HERE ↑↑↑ } catch (err) { throw err; } } ///////////////////////////////////////////////////////////////////////////////////////////////////// // Function - Get Track Group Props function getTrackGroups() { // Declare Group List const groupList = sf.ui.proTools.mainWindow.tables.whoseTitle.is("Group List").first; const groupRows = groupList.childrenByRole("AXRow"); sf.ui.proTools.groupsEnsureGroupListIsVisible(); // Map Groups const groupsList = groupRows.allItems.map(group => { const groupSelectionBtn = group.childrenByRole('AXCell').first.buttons.first; const groupTitleBtnName = group.childrenByRole('AXCell').allItems[1].buttons.first.value.invalidate().value; return { name: groupTitleBtnName.split(' - ')[1], groupID: groupTitleBtnName.split(' - ')[0].split(' ').slice(-1)[0], groupSelectionBtn: groupSelectionBtn, } }); return groupsList; }; ///////////////////////////////////////////////////////////////////////////////////////////////////// function selectTracksInGroup(groupname) { // Collect session group information let groups = getTrackGroups(); // Showing all tracks to make sure the tracks in the group are ready to be processed sf.ui.proTools.mainWindow.trackListPopupButton.popupMenuSelect({ menuPath: ["Show All Tracks"] }); // Filter for the desired group groups.filter(name => name.name === groupname)[0].groupSelectionBtn.elementClick(); let selectedTracks = sf.ui.proTools.selectedTracks; // Ensure those tracks are visible so you can start processing them. ensureAnAudioTrackIsSelectedAndVisible(selectedTracks); // Uncomment if you want to restore previously shown tracks sf.ui.proTools.mainWindow.trackListPopupButton.popupMenuSelect({ menuPath: ["Restore Previously Shown Tracks"] }); } /// Borrowing from Raphael Sepulveda https://forum.soundflow.org/-6034#post-9 to make sure tracks are in view /** * @param {object} arg * @param {AxPtTrackHeader[]} [arg.tracks] - Optional. If provided gets top most from those tracks. */ function determineTopMostEditTrack({ tracks } = {}) { function getTopMostTrack(tracks) { return tracks.filter(h => h.frame.y >= editTimeLineTopY)[0]; } sf.ui.proTools.mainWindow.invalidate(); const editTimeLineTopY = sf.ui.proTools.mainWindow.timelineFocusButton.frame.y; return getTopMostTrack(tracks || sf.ui.proTools.visibleTrackHeaders); }; /** @param {AxPtTrackHeader} track */ function isAudioTrack(track) { return track.title.value.endsWith('Audio Track '); } function ensureAnAudioTrackIsSelectedAndVisible(audioTracks) { if (!sf.ui.proTools.selectedTrackCount || !isAudioTrack(sf.ui.proTools.selectedTrack)) { determineTopMostEditTrack({ tracks: audioTracks }).trackScrollToView(); } else { sf.ui.proTools.selectedTrack.trackScrollToView(); } }
- PPhilip weinrobe @Philip_weinrobe
ok! failure rate much better. still some random crashes, but not that bad. will keep an eye on it and see if i can figure anything out but using it as-is and it's wonderful and a HUGE timesaver. maybe i'll make a video of it in action. pretty fun to watch :)
THANK YOU!
Chad Wahlbrink @Chad2023-08-18 12:16:41.497Z
Stoked it's working @Philip_weinrobe! If you notice any patterns with it failing, let me know and we can try to dial it in a bit more.
-