copying and pasting various info / learning java
Hi, so I've been using Soundflow via macros and am now trying to learn a little javascript for some session prep tasks I can't find macros for. One thing that I'm struggling with is figuring out ways to copy and paste various things such as a track's output to a newly created folder, or to copy a selected track's name and then rename a newly created track with that name + a suffix.
Ultimately what I'm trying to write a script for is something that will:
Copy the name (X) and output (Y) of a selected track
create two new aux sends.
put those auxes into a new routing folder
name the folder (X) + "FX"
route the folder to original output (Y)
Thanks!
Joseph
- Raphael Sepulveda @raphaelsepulveda2023-07-02 16:09:51.293Z
@Joseph_Faison, welcome to SoundFlow, and congratulations on starting your scripting journey!
I can get you started by helping you with your first request.
This is how you get a selected track's name and output and store it in a variable:
const selectedTrack = sf.ui.proTools.selectedTrack; const selectedTrackName = selectedTrack.normalizedTrackName; const selectedTrackOutputName = selectedTrack.outputPathButton.value.value;
Now you can use those variables later in your script, for example, when you get to the point of naming the Routing Folder, you can do this:
sf.ui.proTools.windows.whoseTitle.is("Move To New Folder").first .textFields.whoseTitle.is("Track name").first .elementSetTextFieldWithAreaValue({ value: selectedTrackName + " FX" });
Hopefully, this gets you going to the next step.
- JJoseph Faison @Joseph_Faison
thank you!
- FIn reply toJoseph_Faison⬆:Forrester Savell @Forrester_Savell
Hi Joseph
I've had a go at creating a script for you. Let me know if this works for you and does what you're after?
sf.ui.proTools.appActivateMainWindow() sf.ui.proTools.invalidate() //Get selected Tracks const selectedTracks = sf.ui.proTools.selectedTrackNames let firstTrack = selectedTracks[0]; let folderName = firstTrack + " FX"; // define track output selector const audioOutput = sf.ui.proTools.selectedTrack.groups.whoseTitle.is("Audio IO") const trackOutput = audioOutput.first.popupButtons.allItems[1] // get ouput of first track var outputPaths = trackOutput.popupMenuFetchAllItems().menuItems const outputPath = outputPaths.map((mi, i) => ({ menuItem: mi, index: i })).filter(m => m.menuItem.element.isMenuChecked)[0].menuItem.path; sf.ui.proTools.appActivateMainWindow(); function createAuxes() { sf.ui.proTools.menuClick({ menuPath: ['Track', 'New...'] }); const newTrackWin = sf.ui.proTools.windows.whoseTitle.is("New Tracks").first newTrackWin.elementWaitFor(); const numberField = newTrackWin.textFields.whoseTitle.is('Number of new tracks').first; const numberOfTracks = '2'; if (numberField.value.invalidate().value.trim() !== numberOfTracks) { numberField.elementClick(); sf.keyboard.type({ text: numberOfTracks }); var i = 0; while (numberField.value.invalidate().value !== numberOfTracks) { sf.wait({ intervalMs: 30 }); }; } newTrackWin.popupButtons.whoseDescription.is("Track format").first.popupMenuSelect({ menuPath: ["Stereo"] }); newTrackWin.popupButtons.whoseDescription.is("Track type").first.popupMenuSelect({ menuPath: ["Aux Input"] }); newTrackWin.textFields.whoseTitle.is("Track Name").first.elementClick(); newTrackWin.textFields.whoseTitle.is("Track Name").first.elementSetTextFieldWithAreaValue({ value: 'FX Aux', }); newTrackWin.buttons.whoseTitle.is("Create").first.elementClick(); }; function createFolder() { sf.ui.proTools.menuClick({ menuPath: ["Track", "Move to New Folder..."], }); const newFolderWin = sf.ui.proTools.windows.whoseTitle.is("Move To New Folder").first newFolderWin.elementWaitFor() newFolderWin.popupButtons.first.popupMenuSelect({ menuPath: ["Routing Folder"], }); newFolderWin.checkBoxes.whoseTitle.is("Route Tracks to New Folder").first.checkboxSet({ targetValue: "Enable" }); newFolderWin.textFields.whoseTitle.is("Track Name").first.elementSetTextFieldWithAreaValue({ value: folderName, }); newFolderWin.popupButtons.whoseDescription.is("Track format").first.popupMenuSelect({ menuPath: ["Stereo"] }); newFolderWin.buttons.whoseTitle.is("Create").first.elementClick(); }; createAuxes(); createFolder(); sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.trackSelectByName({ names: [folderName] }); sf.ui.proTools.selectedTrack.groups.whoseTitle.is("Audio IO").first. popupButtons.whoseTitle.contains("Audio Output").first.popupMenuSelect({ menuPath: outputPath, });
- FForrester Savell @Forrester_Savell
Hey @raphaelsepulveda
Would you be able to critique my script? I can see we've used different ways to store track names and outputs. Is there anywhere else I can trim things down and make them more efficient?
I have noticed that my script seems to lag between the createFolder function and the folder selection.- JJoseph Faison @Joseph_Faison
Wow thanks Forrester! This is working well. The way you are defining the output seems to be working better, I'm hitting a snag doing it as
selectedTrack.outputPathButton.value.value
as when I'm routing to vocal(stereo) it logs it as vox (which is how pro tools displays). Not having that issue here.
Raphael Sepulveda @raphaelsepulveda2023-07-03 17:01:11.468Z
@Joseph_Faison, @Forrester_Savell really came through for you. Love to see it!
Yes, if you look a the reply I left for Forrester, we took different approaches on how to manage the output assignments. I usually grab the name of the output first, in the most un-intrusive way, and figure out the path later when assigning the output to the track.
Here's what I mean, this is just the
main
function of the refactored version I left for Forrester but with output assignment being managed the way I described earlier:function main() { sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); //Get selected Tracks const selectedTrack = sf.ui.proTools.selectedTrack; const selectedTrackName = selectedTrack.normalizedTrackName; const selectedTrackOutputName = selectedTrack.outputPathButton.value.value; createAuxes({ numOfAuxes: 2, auxName: "FX Aux" }); createFolder({ folderName: selectedTrackName + " FX", isSelectFolderOnly: true }); // Assign output on selected track sf.ui.proTools.selectedTrack.groups.whoseTitle.is("Audio IO").first .popupButtons.whoseTitle.contains("Audio Output").first .popupMenuSelect({ menuSelector: menuItems => menuItems.find(menuItems => { const path = menuItems.path; return path[path.length - 1].includes(selectedTrackOutputName); }) }); } main();
The main difference is that this approach doesn't need to open the output popup menu of the first selected track at the beginning of the script just to get the path and then close it again, and therefore it looks more transparent when running. On the flip side, my approach as it is right now could behave unexpectedly if the session has busses similarly named, while Forrester's grabs the exact path the original track had.
Hope that makes sense!
- In reply toForrester_Savell⬆:
Raphael Sepulveda @raphaelsepulveda2023-07-03 16:47:16.438Z2023-07-03 22:21:43.011Z
@Forrester_Savell, that's awesome! You really came through for Joseph on this one. Very cool!
I'm happy to review this for you. I went through it quickly and here are the things that stood out:
- Avoid using
var
.- Use
const
most of the time, if the contents of the variable are not going to change. If they are going to change, then uselet
.
- Use
- Function declarations
- Place all function declarations at the top of the script
- No need for a semicolon at the end of a function declaration
- Use a
main
function- This is the function where the important parts of the script happen, usually in a concise way. This has several benefits and the main one is that it increases readability and relieves, what coders call, cognitive load. When you scan down the
main
function, you should be able to quickly understand what it is doing without getting into the details (check the refactored version below to see what I mean).
- This is the function where the important parts of the script happen, usually in a concise way. This has several benefits and the main one is that it increases readability and relieves, what coders call, cognitive load. When you scan down the
- Invalidate the
mainWindow
after creating a new track with a script- This is to refresh SoundFlow's cache and have it recognize the new tracks. This is especially important if you're doing something to the newly created track within the runtime of the script.
- Avoid global variables
- Pass them to the functions directly instead
- In your script, this happened in
createFolder()
which was referring tofolderName
, located outside of its scope. - This is one of the rules of a paradigm called Functional Programming (FP for short), which is cool cause it avoids all kinds of trouble. I don't follow FP all of the time but it's good to consider.
- This also makes your functions modular and can be reused in other scripts
Those are the main things!
To answer some of your other questions:
- Yeah, the way we stored track names and outputs was different but not too different. In my approach, I wanted to extract data from just the first selected track, while you fetched the names of all the selected tracks and then chose the first one from there. As for the output, I think you did awesome by grabbing the path directly from the track. I usually just grab the name and then figure out the path later while assigning it to the track—example of this in another reply I'm leaving in response to Joseph.
- Yes, that lag you're talking about is PT figuring out the routing behind the scenes. If you do the steps manually and try to do something immediately after, like select other tracks, or change edit selection, you'll see PT is super sluggish for a few seconds. SoundFlow waits patiently until PT is ready to be automated again before proceeding to select the folder.
After all that been said, here's how I'd refactor your script:
/** @param {{ numOfAuxes: number, auxName: string }} args */ function createAuxes({ numOfAuxes, auxName }) { const newTrackWin = sf.ui.proTools.windows.whoseTitle.is("New Tracks").first; const numberField = newTrackWin.textFields.whoseTitle.is('Number of new tracks').first; sf.ui.proTools.menuClick({ menuPath: ['Track', 'New...'] }); newTrackWin.elementWaitFor(); if (numberField.value.invalidate().value.trim() !== String(numOfAuxes)) { numberField.elementClick(); sf.keyboard.type({ text: String(numOfAuxes) }); var i = 0; while (numberField.value.invalidate().value !== String(numOfAuxes)) { sf.wait({ intervalMs: 30 }); } } newTrackWin.popupButtons.whoseDescription.is("Track format").first.popupMenuSelect({ menuPath: ["Stereo"] }); newTrackWin.popupButtons.whoseDescription.is("Track type").first.popupMenuSelect({ menuPath: ["Aux Input"] }); newTrackWin.textFields.whoseTitle.is("Track Name").first.elementSetTextFieldWithAreaValue({ value: auxName, }); newTrackWin.buttons.whoseTitle.is("Create").first.elementClick(); newTrackWin.elementWaitFor({ waitType: "Disappear" }); sf.ui.proTools.mainWindow.invalidate(); } /** @param {{ folderName: string, isSelectFolderOnly: boolean }} args */ function createFolder({ folderName, isSelectFolderOnly }) { const newFolderWin = sf.ui.proTools.windows.whoseTitle.is("Move To New Folder").first; sf.ui.proTools.menuClick({ menuPath: ["Track", "Move to New Folder..."], }); newFolderWin.elementWaitFor() newFolderWin.popupButtons.first.popupMenuSelect({ menuPath: ["Routing Folder"], }); newFolderWin.checkBoxes.whoseTitle.is("Route Tracks to New Folder").first.checkboxSet({ targetValue: "Enable" }); newFolderWin.textFields.whoseTitle.is("Track Name").first.elementSetTextFieldWithAreaValue({ value: folderName, }); newFolderWin.popupButtons.whoseDescription.is("Track format").first.popupMenuSelect({ menuPath: ["Stereo"] }); newFolderWin.buttons.whoseTitle.is("Create").first.elementClick(); newFolderWin.elementWaitFor({ waitType: "Disappear" }); sf.ui.proTools.mainWindow.invalidate(); if (isSelectFolderOnly) { sf.ui.proTools.trackSelectByName({ names: [folderName] }); } } function getSelectedTrackOutputPath() { // define track output selector const trackOutput = sf.ui.proTools.selectedTrack.groups.whoseTitle.is("Audio IO").first .popupButtons.allItems[1]; const outputPaths = trackOutput.popupMenuFetchAllItems().menuItems; const outputPath = outputPaths .map((mi, i) => ({ menuItem: mi, index: i })) .filter(m => m.menuItem.element.isMenuChecked)[0].menuItem.path; sf.ui.proTools.appActivateMainWindow(); return outputPath; } function main() { sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); //Get selected Tracks const selectedTrackName = sf.ui.proTools.selectedTrackNames[0]; const outputPath = getSelectedTrackOutputPath(); createAuxes({ numOfAuxes: 2, auxName: "FX Aux" }); createFolder({ folderName: selectedTrackName + " FX", isSelectFolderOnly: true }); // Assign output on selected track sf.ui.proTools.selectedTrack.groups.whoseTitle.is("Audio IO").first .popupButtons.whoseTitle.contains("Audio Output").first .popupMenuSelect({ menuPath: outputPath, }); } main();
- FForrester Savell @Forrester_Savell
Wow @raphaelsepulveda this is immensely helpful, thanks for taking the time to write all that out. That is a great list of tips I didn't pickup watching hours of Javascript video. I have a bit of refactoring to do in my other scripts!
Raphael Sepulveda @raphaelsepulveda2023-07-03 22:24:13.303Z
Glad that was helpful. You're doing great!
- Avoid using