No internet connection
  1. Home
  2. How to

How to create incremental bounce regions

By danielkassulke @danielkassulke
    2022-10-02 02:08:10.239Z2022-10-02 07:09:16.115Z

    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,

    • 18 replies

    There are 18 replies. Estimated reading time: 22 minutes

    1. samuel henriques @samuel_henriques
        2022-10-04 22:29:17.788Z2023-03-03 10:32:22.333Z

        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)
        
        1. Ddanielkassulke @danielkassulke
            2023-05-05 21:10:04.513Z

            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
            });
            
          • D
            In reply todanielkassulke:
            danielkassulke @danielkassulke
              2022-10-04 23:00:56.923Z

              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?

              1. samuel henriques @samuel_henriques
                  2022-10-04 23:05:25.350Z

                  and you would like a marker memory location and a selection memory location for each clip?

                  1. Ddanielkassulke @danielkassulke
                      2022-10-04 23:13:57.863Z

                      That would be ideal!

                      1. samuel henriques @samuel_henriques
                          2022-10-04 23:29:41.929Z

                          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?

                          1. Ddanielkassulke @danielkassulke
                              2022-10-04 23:37:13.447Z

                              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?

                              1. samuel henriques @samuel_henriques
                                  2022-10-04 23:40:02.154Z

                                  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?

                                  1. samuel henriques @samuel_henriques
                                      2022-10-04 23:47:57.495Z2022-10-04 23:57:09.525Z

                                      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.

                          2. D
                            In reply todanielkassulke:
                            danielkassulke @danielkassulke
                              2022-10-04 23:54:28.876Z

                              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?

                              1. samuel henriques @samuel_henriques
                                  2022-10-05 08:36:38.474Z

                                  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.

                                  1. Ddanielkassulke @danielkassulke
                                      2022-10-05 09:31:19.843Z

                                      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!

                                  2. D
                                    In reply todanielkassulke:
                                    danielkassulke @danielkassulke
                                      2023-02-28 04:04:08.381Z

                                      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.

                                      1. samuel henriques @samuel_henriques
                                          2023-03-02 17:59:47.252Z

                                          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

                                          1. Ddanielkassulke @danielkassulke
                                              2023-03-02 19:47:30.102Z

                                              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

                                              1. samuel henriques @samuel_henriques
                                                  2023-03-03 10:33:21.578Z2023-03-03 12:13:44.661Z

                                                  Thanks Daniel,

                                                  I made a small change on the first script, try and let me know.

                                                  1. Ddanielkassulke @danielkassulke
                                                      2023-03-04 21:52:40.272Z

                                                      Hey Samuel, it works perfectly now, thanks!

                                              2. D
                                                In reply todanielkassulke:
                                                danielkassulke @danielkassulke
                                                  2023-04-27 08:10:48.731Z

                                                  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
                                                  });