How to create incremental bounce regions
Hi team,
Long time listener, first time caller on PT 2022.9 - and just dipping my toes into some coding for the first time. I work regularly with classical / jazz ensembles that record without click. As the sessions move so fast, it's often more efficient for me to leave the session in record and label take start/end times with markers than creating new playlists. Session tidying / clip splitting gets done afterwards. My normal workflow is to send all takes to the artist to review and have them give me editing notes based on those takes. My current method of bouncing these takes is to create a new marker selection for each take, name it T1 for take 1, T2 for take 2 etc, then be able to recall each marker selection with the .0. command on the numeric keypad. Because all takes are tracks of identical length, I would love to find a solution that enables me to automatically create and incrementally name marker selections for every take in a given timeline.
A massive bonus would be to bounce each selection after naming it, or to bounce all selections after the marker selection naming process is complete.
I assume it would look something like this:
Open Protools main window
Highlight clip
Open Create Memory Location dialogue
Toggle Time Properties to: Selection
Name T+1 [T for take]
Confirm name
Lather rinse and repeat for subsequent clips.
I'm guessing this functionality hinges entirely on the clipDoForEachClipInTrack. As a total noob I've had a crack at this today but stumbled into a few roadblocks. Would appreciate any input or even advice on whether this is possible!
Cheers,
- samuel henriques @samuel_henriques
Hello Daniel,
From what I understood, I'm thinking two scripts would be good here.
One script to create the memory locations with increment number.
Create Selection Memory Locations(auto name):function getNextTakeName() { const currentTakes = sf.ui.proTools.memoryLocationsWindow.tables.whoseTitle.is("Memory Locations").first.invalidate().children.whoseRole.is("AXRow").map(l => ({ number: l.children.whoseRole.is("AXCell").allItems[0].children.whoseRole.is("AXStaticText").first.title.value.replace("Selected. ", ""), name: l.children.whoseRole.is("AXCell").allItems[1].children.whoseRole.is("AXStaticText").first.title.value.replace("Selected. ", ""), nameElement: l.children.whoseRole.is("AXCell").allItems[1].children.whoseRole.is("AXStaticText").first })).filter(ml => ml.name.match(/T\d+/)) if (currentTakes.length === 0) return "T01" let lastTake = Math.max(...currentTakes.map(ml => (+ml.name.split("T")[1].replace(" BNC", "")))) // return T & increment return "T" + (lastTake < 9 ? "0" + ++lastTake : ++lastTake) }; function newMemoryLocation({ type, name }) { sf.keyboard.press({ keys: "numpad enter" }); const memLocWin = sf.ui.proTools.newMemoryLocationDialog.elementWaitFor().element //Set new name memLocWin.textFields.allItems[1].elementSetTextFieldWithAreaValue({ value: name }); if (type === "marker") { // Set selection memLocWin.radioButtons.whoseTitle.is("Marker").first.checkboxSet({ targetValue: "Enable" }) } else if (type === "selection") { // Set selection memLocWin.radioButtons.whoseTitle.is("Selection").first.checkboxSet({ targetValue: "Enable" }) } memLocWin.buttons.whoseTitle.is("OK").first.elementClick(); memLocWin.elementWaitFor({ waitType: "Disappear" }) }; sf.ui.proTools.invalidate(); sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.memoryLocationsEnsureWindow({ action: () => { sf.ui.proTools.clipDoForEachSelectedClip({ action: () => { const nextName = getNextTakeName(); newMemoryLocation({ type: "marker", name: nextName }); newMemoryLocation({ type: "selection", name: (nextName + " BNC") }); } }) }, restoreWindowOpenState: true });
Then, once all the selections have been created, you run a script to bounce them all.
As it is, it will first ask for a song name, the name will be used to all the takes and is formatting the bounce file name as "Song Name T01" for the first take, then increment.
Then you'll get a list of all the takes on the session, and you can choose as many as you want.
Then you'll get to the bounce mix window.
It will stop on the bounce mix dialog so you can set your settings, we can talk later about how to automate this as well. Once you press bounce, the script will want for it to finish, select the next take from the list you chose and bounce that one, stoping again so you can set your settings.Test it out and let me know how it goes;
function getMemLocTakes() { return sf.ui.proTools.memoryLocationsWindow.tables.whoseTitle.is("Memory Locations").first.children.whoseRole.is("AXRow").map(l => ({ number: l.children.whoseRole.is("AXCell").allItems[0].children.whoseRole.is("AXStaticText").first.title.value.replace("Selected. ", ""), name: l.children.whoseRole.is("AXCell").allItems[1].children.whoseRole.is("AXStaticText").first.title.value.replace("Selected. ", ""), type: l.children.whoseRole.is("AXCell").allItems[3].children.whoseRole.is("AXStaticText").first.title.value, nameElement: l.children.whoseRole.is("AXCell").allItems[1].children.whoseRole.is("AXStaticText").first })).filter(ml => ml.name.match(/T\d+/) && ml.type === "Selection") }; function bounce(fileName) { sf.ui.proTools.menuClick({ menuPath: ["File", "Bounce Mix..."] }); const bounceMixWin = sf.ui.proTools.windows.whoseTitle.is("Bounce Mix").first.elementWaitFor({}, 'Could not find Bounce Mix Win').element bounceMixWin.textFields.first.elementSetTextFieldWithAreaValue({ value: fileName.trim() }) bounceMixWin.elementWaitFor({ waitType: "Disappear", timeout: -1 }); //Wait for bounce to finish sf.ui.proTools.confirmationDialog.elementWaitFor({ waitType: 'Disappear', timeout: -1 }); //-1 is endless timeout (cancel by Ctrl+Shift+Esc) }; function setSongName(name) { typeof name == "string" ? name : name = "" return sf.interaction.displayDialog({ defaultAnswer: name || "", buttons: ["Cancel", "Bounce!"], defaultButton: "Bounce!", cancelButton: "Cancel", title: "Song Name", prompt: "Type the Song Name" }); }; function bounceTakes(songName) { const takes = getMemLocTakes() const takeNames = takes.map(el => el.name) const takesToBonce = sf.interaction.selectFromList({ prompt: "Choose Masters to Commit:", title: "Available Masters", defaultItems: takeNames, items: takeNames, allowMultipleSelections: true, onCancel: "Continue", }).list takesToBonce.forEach(take => { const takeMemLoc = takes.filter(t => t.name === take)[0] sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: +takeMemLoc.number }) bounce(songName + " " + takeMemLoc.name) }) }; sf.ui.proTools.invalidate(); sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.memoryLocationsShowWindow() const songNameDlg = setSongName(globalState.songNameBackBouceMarkers) globalState.songNameBackBouceMarkers = songNameDlg.text bounceTakes(songNameDlg.text)
- Ddanielkassulke @danielkassulke
Hi Samuel - thought you might be interested in this modification. It's the same as before, but it auto-numbers the markers and marker selections:
function getNextTakeName() { const currentTakes = sf.ui.proTools.memoryLocationsWindow.tables.whoseTitle.is("Memory Locations").first.invalidate().children.whoseRole.is("AXRow").map(l => ({ number: l.children.whoseRole.is("AXCell").allItems[0].children.whoseRole.is("AXStaticText").first.title.value.replace("Selected. ", ""), name: l.children.whoseRole.is("AXCell").allItems[1].children.whoseRole.is("AXStaticText").first.title.value.replace("Selected. ", ""), nameElement: l.children.whoseRole.is("AXCell").allItems[1].children.whoseRole.is("AXStaticText").first })).filter(ml => ml.name.match(/T\d+/)); if (currentTakes.length === 0) return "T01"; let lastTake = Math.max(...currentTakes.map(ml => (+ml.name.split("T")[1].replace(" BNC", "")))); // return T & increment return "T" + (lastTake < 9 ? "0" + ++lastTake : ++lastTake); } function newMemoryLocation({ type, name }) { sf.keyboard.press({ keys: "numpad enter" }); const memLocWin = sf.ui.proTools.newMemoryLocationDialog.elementWaitFor().element; const markerNumber = parseInt(memLocWin.textFields.whoseValue.contains("Location").first.value.value.replace("Location ", ""), 10); const markerName = `${markerNumber}. ${name}`; memLocWin.textFields.allItems[1].elementSetTextFieldWithAreaValue({ value: markerName }); if (type === "marker") { memLocWin.radioButtons.whoseTitle.is("Marker").first.checkboxSet({ targetValue: "Enable" }); } else if (type === "selection") { memLocWin.radioButtons.whoseTitle.is("Selection").first.checkboxSet({ targetValue: "Enable" }); } memLocWin.buttons.whoseTitle.is("OK").first.elementClick(); memLocWin.elementWaitFor({ waitType: "Disappear" }); } sf.ui.proTools.invalidate(); sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.memoryLocationsEnsureWindow({ action: () => { sf.ui.proTools.clipDoForEachSelectedClip({ action: () => { const nextName = getNextTakeName(); newMemoryLocation({ type: "marker", name: nextName }); newMemoryLocation({ type: "selection", name: (nextName + " BNC") }); } }); }, restoreWindowOpenState: true });
- DIn reply todanielkassulke⬆:danielkassulke @danielkassulke
Hey Samuel,
Thanks so much! This is a really promising start, and already an amazing time saver. Note I've only tested the "create marker selection" script so far, but I wonder if there is one extra tweak that could be made: The markers are either dropped as a pseudo-selection cursor location or a normal selection, depending on whether you've selected the clip or have the playhead beside the clip. I wonder if the functionality of the "Do all to each selected clip" could actually automate that process of selecting clips, as seen in the video below?
samuel henriques @samuel_henriques
and you would like a marker memory location and a selection memory location for each clip?
- Ddanielkassulke @danielkassulke
That would be ideal!
samuel henriques @samuel_henriques
But then you'll still want to select each selection marker and bounce, correct? This is giving me the problem with two markers with the same name.
If you were asking for the selection markers so you could then select them to bounce only, we can skip that part.
And I can make it create the markers and bounce each selected clip, all in one go and not two script?- Ddanielkassulke @danielkassulke
Yeah - each selection marker would be intended for bouncing files, and having those two paired in one script is great. Typically I'd use markers locations just to label takes (e.g. T01, T02 etc), and marker selections to bounce. I'd typically name those marker selections e.g. T01 BNC (take 1 bounce) so I can easily differentiate between the marker and marker selection in the memory location window. Would modifying the naming convention would circumvent that issue on your end?
samuel henriques @samuel_henriques
In the mean time I sorted the repeat names thing. :)
Let me add to the selection name BNC
where do you take the song name from? could it be extracted from the session name?
how does the final bounce name look like?
samuel henriques @samuel_henriques
Have to go now, just updated as you asked, marker and selection memLoc + BNC
and the bounce script will work with the new markers as well. it will only show as available selection markers independent of the name
One important detail, if your clips have fades we need to do this in a different way, doForEachSelectClip doesn't work well with fades.
- DIn reply todanielkassulke⬆:danielkassulke @danielkassulke
Really impressed, Samuel!
You are a genius! This is a massive game-changer. Song name is tricky because in a session like this, typically there'll be multiple in one session. Typically I'd just extract the song names from the marker locations (e.g. T01, T02, T03), rather than naming each individual bounce. The bounced file name would be just be "T01", etc. It only needs to be simple for the editing process!
Do you think it would be possible to automate the above to all selected clips in a timeline?
samuel henriques @samuel_henriques
Awesome, thank you.
The goal is to automate it all.
Try it as it is and we'll figure out the other details. The bounce script is asking for a name, leave it blank for now.- Ddanielkassulke @danielkassulke
The marker selection script is perfect. Will let you know if it behaves oddly, but so far it does exactly what I hoped it would!
- DIn reply todanielkassulke⬆:danielkassulke @danielkassulke
Hi @samuel_henriques - I have just noticed that this script goes awry - presumably due to switching over to PT 2022.12. The script is failing at line 57 - I'm wondering if the nomenclature of doForEachSelectedClip has changed in the back end? The script now fails after executing the code on only the first clip.
samuel henriques @samuel_henriques
Hello Daniel,
I'm on 2022.12 and both scripts are working. Could you make a screen capture of the script failing so I can try to figure what is wrong?
Thank you
- Ddanielkassulke @danielkassulke
Hi Samuel. Here's an empty PT session with no previous memory locations or audio files. I should clarify that the error is only happening to the script that adds memory locations and markers https://www.dropbox.com/sh/18slc2rb4nfiuel/AAAscm5AkyV2MR35t8FbkzRga?dl=0
samuel henriques @samuel_henriques
Thanks Daniel,
I made a small change on the first script, try and let me know.
- Ddanielkassulke @danielkassulke
Hey Samuel, it works perfectly now, thanks!
- DIn reply todanielkassulke⬆:danielkassulke @danielkassulke
I have found this slight modification to @samuel_henriques' script really great to label sessions. The way I use it is with a streamdeck loaded with marker presets, i.e. intro, verse 1, pre-chorus, and drop these marker presets during a session. Handy for film+TV session spotting, and great for labeling takes if you're recording across the timeline rather than using playlists. The marker name incorporates the marker number as a string. For anyone who uses the numpad to navigate around the session, having the marker number included in the marker name it saves you from needing to have the memory location window kept open.
// Returns the name of the next memory location with an incremented number and a suffix. function getNextTakeName() { // Define the suffix you want to add to the marker name. const suffix = " YOUR MARKER"; // Replace " YOUR MARKER" with the desired suffix // Get a list of memory locations and their properties from Pro Tools. const currentTakes = sf.ui.proTools.memoryLocationsWindow.tables .whoseTitle.is("Memory Locations") .first.invalidate() .children.whoseRole.is("AXRow") .map(l => ({ number: l.children.whoseRole.is("AXCell").allItems[0].children.whoseRole.is("AXStaticText").first.title.value.replace("Selected. ", ""), name: l.children.whoseRole.is("AXCell").allItems[1].children.whoseRole.is("AXStaticText").first.title.value.replace("Selected. ", ""), nameElement: l.children.whoseRole.is("AXCell").allItems[1].children.whoseRole.is("AXStaticText").first })); // Extract marker numbers from the memory locations list. const markerNumbers = currentTakes.map(take => parseInt(take.number, 10)); // If there are no markers, return the first marker number with the suffix. if (markerNumbers.length === 0) { return "1." + suffix; } // Calculate the next marker number. const nextMarkerNumber = Math.max(...markerNumbers) + 1; // Return the next marker number with the suffix. return nextMarkerNumber + "." + suffix; } function newMemoryLocation({ type, name }) { sf.keyboard.press({ keys: "numpad enter" }); const memLocWin = sf.ui.proTools.newMemoryLocationDialog.elementWaitFor().element; memLocWin.textFields.allItems[1].elementSetTextFieldWithAreaValue({ value: name }); // If the memory location type is a marker, enable the "Marker" radio button. if (type === "marker") { memLocWin.radioButtons.whoseTitle.is("Marker").first.checkboxSet({ targetValue: "Enable" }); } memLocWin.buttons.whoseTitle.is("OK").first.elementClick(); memLocWin.elementWaitFor({ waitType: "Disappear" }); } sf.ui.proTools.invalidate(); sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.memoryLocationsEnsureWindow({ action: () => { const nextName = getNextTakeName(); newMemoryLocation({ type: "marker", name: nextName }); }, restoreWindowOpenState: true });