No internet connection
  1. Home
  2. How to

A script that takes the text from a marker above and uses a clip group to bounce section link, on loop!

By Dane Butler @Dane_Butler
    2023-11-16 21:51:23.468Z

    Hey @Chad !

    I will send a video via email because I don't want the specifics of the content to be super public and it might be somewhat difficult to explain otherwise. I'm looking for a script that will automate exporting commercial audio spots. There are quite a few that need to be named and bounced, and the length is dictated by a clipgroup on a track called length, and the name is dictated by a marker at the head of said clip group. Basically the script would bounce every clip group denoted region using the accompanying marker as the naming guide. Do you think this is possible? Or would it work better if the name was on the clip group itself?

    Solved in post #4, click to view
    • 15 replies

    There are 15 replies. Estimated reading time: 19 minutes

    1. Chad Wahlbrink @Chad2023-11-16 23:04:42.361Z

      Hey @Dane_Butler!

      Thank you for making a forum post. If you can, can you give a bit more detail of the steps you are looking to automate?

      From what I can see here, it seems like you'd like to:

      1. Set a timeline selection based on a clip group
      2. Capture the name of a Memory Location on the global ruler
      3. Click "File > Bounce Mix..."
      4. Ensure "Add Mp3" is selected
      5. Click "Bounce"
      6. Click "OK" on the MP3 dialog.
      7. Wait for the bounce to complete
      8. Repeat for each selected clip

      I believe each of those steps should be automatable. I'll see if I can work up a script in the next few days.

      1. DDane Butler @Dane_Butler
          2023-11-17 00:00:30.554Z

          Yes, I believe you nailed it! I have lots of regions to bounce, and each region is its own self contained mix!

          The ability to automate the renaming and exporting process would be extremely helpful for these radio ads!

        • In reply toDane_Butler:
          Chad Wahlbrink @Chad2023-11-17 00:03:27.462Z

          @Dane_Butler, give this a shot. This script should perform the steps outlined above. It is good practice to do a "test bounce" and make sure all the bounce settings are set up how you want them for the WAV/MP3 before bouncing. Then, select all of the clip groups on the "length" track and run the script.

          Note: This script doesn't currently overwrite files if they exist. So it's best to delete files in the "bounced files" folder if you are "re-printing."

          if (!sf.ui.proTools.isRunning) throw `Pro Tools is not running`;
          
          sf.ui.proTools.appActivateMainWindow();
          sf.ui.proTools.mainWindow.invalidate();
          
          let memLocs;
          // Convert Pro Tools Version to Number
          const ptVersion = sf.ui.proTools.appVersion.split('.').map(Number).slice(0, 2).reduce((p, c, i) => p + Math.pow(100, 2 - i) * c, 0);
          
          function getMemoryLocations() {
          
              // 2023.03 to 2023.06 support
              const tableIndex = ptVersion >= 230600 ? 1 : 0;
              const memLocTableNameField = ptVersion >= 230600 ? 2 : 1;
              const memLocTableTimeCodeField = ptVersion >= 230600 ? 4 : 2;
          
              sf.ui.proTools.appActivate();
              sf.ui.proTools.mainWindow.elementRaise();
              var i = 0;
          
              // Open the Memory Locations Window
              while (i++ < 5) {
                  if (!sf.ui.proTools.memoryLocationsWindow.invalidate().exists) {
                      sf.ui.proTools.getMenuItem('Window', 'Memory Locations').elementClick({}, 'Could not click to open Memory Locations window');
                      sf.ui.proTools.memoryLocationsWindow.elementWaitFor();
                  }
          
                  // Define the memory locations table
                  var table = sf.ui.proTools.memoryLocationsWindow.invalidate().tables[tableIndex].children.whoseRole.is("AXRow");
                  if (!table.exists) {
                      sf.ui.proTools.memoryLocationsWindow.windowClose();
                  }
              }
              if (!table.exists) throw 'Could not find memory locations table';
          
              // define the memory locations items
              var items = [];
              table.forEach(function (item, i) {
                  var memLocNum = item.children.whoseRole.is("AXCell").allItems[1].children.whoseRole.is("AXStaticText").first.title.value.replace("Selected. ", "");
                  var memLocName = item.children[memLocTableNameField].children.whoseRole.is("AXStaticText").first.title.value.replace("Selected. ", "");
                  var timeStart = item.children[memLocTableTimeCodeField].children.whoseRole.is("AXStaticText").first.title.value.replace("Selected. ", "");
                  if (memLocNum === null || memLocName === null) return;
                  items.push({
                      memLocNum,
                      memLocName,
                      timeStart
                  });
              });
              return items;
          }
          
          function bounceMix(bounceName) {
              // Click "Bounce Mix..." from File Menu
              sf.ui.proTools.menuClick({
                  menuPath: ["File", "Bounce Mix..."],
              }, `Could not click "Bounce Mix" in Pro Tools`);
          
              // Define Bounce Window
              let bounceWin = sf.ui.proTools.windows.whoseTitle.is("Bounce Mix").first;
          
              // Wait for Bounce Window to Appear
              bounceWin.elementWaitFor({
                  waitType: "Appear",
              });
          
              // Set Track Name
              sf.ui.proTools.windows.whoseTitle.is("Bounce Mix").first.textFields.first.elementSetTextFieldWithAreaValue({ value: bounceName });
          
              // Check the MP3 checkbox
              if (!sf.ui.proTools.windows.whoseTitle.is("Bounce Mix").first.groups.whoseTitle.is("Audio").first.checkBoxes.whoseTitle.is("Add MP3").first.isCheckBoxChecked) {
                  sf.ui.proTools.windows.whoseTitle.is("Bounce Mix").first.groups.whoseTitle.is("Audio").first.checkBoxes.whoseTitle.is("Add MP3").first.checkboxSet({ targetValue: "Enable" })
              }
          
              // Click Bounce
              bounceWin.buttons.whoseTitle.is("Bounce").first.elementClick();
          
              // Wait for the MP3 Dialog
              sf.ui.proTools.windows.whoseTitle.is("MP3").first.elementWaitFor({ waitType: "Appear" });
          
              // Click "OK"
              sf.ui.proTools.windows.whoseTitle.is("MP3").first.buttons.whoseTitle.is("OK").first.elementClick();
          
              // Wait for the MP3 Dialog to Disappear
              sf.ui.proTools.windows.whoseTitle.is("MP3").first.elementWaitFor({ waitType: "Disappear" });
          
              // Wait for Bounce Window to Disappear
              bounceWin.elementWaitFor({ waitType: "Disappear", timeout: -1, onError: "Continue" });
          
              // Wait for Bounce to finish (Wait for bounce dialog to appear, then to disappear)
              var bouncingLabel = sf.ui.proTools.windows.whoseTitle.is('').first.children.whoseRole.is("AXStaticText").whoseValue.is('Bouncing...').first;
              bouncingLabel.elementWaitFor({ timeout: 1000, onError: "Continue" });
              bouncingLabel.elementWaitFor({ waitType: 'Disappear', timeout: -1 }); // -1 is endless timeout (cancel by Ctrl+Shift+Esc)
          }
          
          function bounceEachClip() {
              // Get the current timecode 
              let currentTimeCode = sf.ui.proTools.invalidate().mainWindow.counterDisplay.textFields.whoseTitle.is("Edit Selection Start").first.value.value;
          
              // find the memory location with the current timecode
              let currentMemoryLocationName = memLocs.filter(x => x.timeStart === currentTimeCode)[0].memLocName;
          
              // bounce the mix
              bounceMix(currentMemoryLocationName);
          }
          
          function main() {
              // Set Main Counter to Samples
              if (!sf.ui.proTools.getMenuItem('View', 'Main Counter', 'Samples').isMenuChecked) {
                  sf.ui.proTools.menuClick({ menuPath: ['View', 'Main Counter', 'Samples'], });
              }
          
              try {
                  // Get the memory locations
                  memLocs = getMemoryLocations();
          
                  // bounce each selected clip
                  sf.ui.proTools.clipDoForEachClip({ action: bounceEachClip })
          
              } finally {
                  // Set Main Counter to Timecode
                  if (!sf.ui.proTools.getMenuItem('View', 'Main Counter', 'Timecode').isMenuChecked) {
                      sf.ui.proTools.menuClick({ menuPath: ['View', 'Main Counter', 'Timecode'], });
                  }
              }
          }
          
          main();
          
          log('finished bouncing');
          
          ReplySolution
          1. DDane Butler @Dane_Butler
              2023-11-17 00:13:23.353Z

              Thanks chad! Impressive stuff as per usual. I'll give this a go when I'm back in front of my main rig.

              Btw, for my edification, what does this line do?

              const ptVersion = sf.ui.proTools.appVersion.split('.').map(Number).slice(0, 2).reduce((p, c, i) => p + Math.pow(100, 2 - i) * c, 0);

              I see it uses some array methods but the .reduce((p,c,i)=> p+ Math.pow(100,2-i)*c,0);

              ...is rather confounding!

              1. Chad Wahlbrink @Chad2023-11-17 00:22:25.231Z

                Honestly, I haven't taken the time to unpack that function either! That little bit of magic was passed onto me by Christian. The reduce method is a fairly complex array method. I'd recommend checking out this video if interested:

                https://youtu.be/6_XzV25rkcE?si=OtcnC10lhYs5Jwh8

                The result is a number version of the current pro tools version. Avid changed up some of the GUI for memory locations starting in Pro Tools 2023.06 when they introduced Track Markers. I am using that to differentiate how the Memory Locations window is read based on the Pro Tools version since I wasn't 100% sure what version you are using.

                1. DDane Butler @Dane_Butler
                    2023-11-17 02:50:52.720Z

                    Ok I just checked it out and it's working! Man I can't thank you enough. These two scripts alone have seriously improved my quality of life. Most appreciated. I'm gonna check out that youtube video now!

                    1. DDane Butler @Dane_Butler
                        2023-11-30 22:09:56.149Z

                        Heeeey @Chad ! I have returned with hopefully the last request for a while. I'm back on another video game, but this time on the voice design side. What I was wondering is....

                        would you be able to create a macro that does something very similar to the last one, but with one key difference! Rather than exporting and copying marker data, the operation would be ....

                        1. Select all clips on a timeline (Clip groups)
                        2. Select each clip individually, and then once a clip is selected...
                        3. Press record until the recording reaches the end of the selected clip (Leaving you with two clips of equal size, the one selected, and the one on the track that was record enabled.
                        4. Go to the next clip and rinse and repeat until all clips have been printed.

                        Basically I have an effect chain that I have to run each individual clip through onto a print track.

                        **Here's where it gets juicy. **

                        Some of the clips I'm printing (depending on the thing I'm designing) will have reverb on them.

                        Would there be a way to have an optional modal that pops up and asks me how much of a "tail" I would like to add on to the end of the file? So if say it's printing and moving between clips that are each 3 seconds long, the modal user prompt would factor in a 1 second tail to be automatically added to the print sequence per clip (for a total length of 4 seconds)? I can send a video offline if need be!

                        1. Chad Wahlbrink @Chad2023-12-01 16:34:37.803Z

                          Hey @Dane_Butler, I'll take a look at this workflow next week. Thanks!

                          1. DDane Butler @Dane_Butler
                              2023-12-08 03:57:35.032Z

                              hey Chad! Just wanted to check in on you! Any progress? :) I super appreciate it again.

                            • In reply toDane_Butler:
                              Chad Wahlbrink @Chad2023-12-08 15:49:27.655Z

                              Hey @Dane_Butler,

                              Here's a solution that is working on my rig currently. It does use the Pro Tools SDK, so if you are on a version of Pro Tools before 2023.6 or so, then I'll need to adjust that aspect to work solely on UI.

                              Here's a little video walking you through how it works:
                              https://www.dropbox.com/s/l858ivjy5c47g0p/2023-12-08 Render Voice Sound Design.mp4?dl=0

                              if (!sf.ui.proTools.isRunning) throw `Pro Tools is not running`;
                              
                              sf.ui.proTools.appActivateMainWindow();
                              sf.ui.proTools.mainWindow.invalidate();
                              
                              function recordClipToNewTrack() {
                                  if (offset != '') {
                                      // SDK - Get Sample Rate
                                      let sR = sf.app.proTools.getSessionSampleRate().sampleRate.split('SR')[1];
                              
                                      //SDK - Capture Starting Selection
                                      let startSelection = sf.app.proTools.getTimelineSelection({ timeScale: "Samples" });
                              
                                      //Add Offset to Selection Out Time
                                      let newStop = Number(startSelection.outTime) + (Number(offset) * Number(sR));
                              
                                      // SDK - Set Timeline Selection with Offset
                                      sf.app.proTools.setTimelineSelection({ inTime: startSelection.inTime, outTime: String(newStop), });
                                  }
                              
                                  //Record Enable
                                  sf.ui.proTools.mainWindow.transportViewCluster.transportButtons.recordEnableButton.elementClick();
                              
                                  //Play to start record
                                  sf.ui.proTools.mainWindow.transportViewCluster.transportButtons.playButton.elementClick();
                              
                                  // Wait for record to finish
                                  while (sf.ui.proTools.isPlaying) {
                                      sf.wait({ intervalMs: 200 });
                                  }
                              
                              }
                              
                              // Using this function to commit fades before printing to better work with doForEachClip()
                              function renderFades() {
                              
                                  // Open Gain Audio Suite
                                  let win = sf.ui.proTools.audioSuiteOpenPlugin({
                                      category: 'Other',
                                      name: 'Gain',
                                  }, `Could not find AudioSuite plugin in menu "AudioSuite" -> "Other" -> "Gain"`).window;
                              
                                  // Set Gain Audio Suite Settings
                                  win.audioSuiteSetOptions({
                                      processingInputMode: "EntireSelection",
                                      processingOutputMode: "CreateIndividualFiles",
                                  });
                              
                                  // Render Gain Audio Suite
                                  win.audioSuiteRender();
                              
                                  // Close Gain Audio Suite
                                  win.windowClose();
                              }
                              
                              function main() {
                              
                                  // Ensure Transport
                                  sf.ui.proTools.transportEnsureCluster();
                              
                                  // Check for record enabled track
                                  if (!sf.ui.proTools.mainWindow.transportViewCluster.groups.whoseTitle.is("Normal Transport Buttons").first.buttons.whoseTitle.is("Track Record Enabled").exists) {
                                      alert(`Please Record Enable a Destination Track`);
                                      throw 0;
                                  }
                              
                                  // Select all Clips
                                  sf.ui.proTools.menuClick({ menuPath: ['Edit', 'Select All'], });
                              
                                  renderFades();
                              
                                  try {
                                      // Record Each Selected Clip
                                      sf.ui.proTools.clipDoForEachClip({ action: recordClipToNewTrack })
                              
                                  } finally {
                                      // Set Main Counter to Timecode
                                      if (!sf.ui.proTools.getMenuItem('View', 'Main Counter', 'Timecode').isMenuChecked) {
                                          sf.ui.proTools.menuClick({ menuPath: ['View', 'Main Counter', 'Timecode'], });
                                      }
                              
                                  }
                              }
                              
                              // Would you like to print extra for reverb? 
                              let offset = prompt(`Would You like To add extra time (in seconds)?`);
                              
                              main();
                              
                              1. DDane Butler @Dane_Butler
                                  2023-12-08 15:52:23.270Z

                                  Legend! I'll fire this up today and see where it gets me! Thank you again, if this works it will save my can on this second batch of DZN characters.

                                  1. DDane Butler @Dane_Butler
                                      2023-12-21 17:43:36.376Z

                                      The script worked awesome! The only thing that threw an error was if I tried to enter a time in seconds into the window! I manually drug the flag to get the post roll to change which was a good workaround. This is very exciting though! Huge leap forward in workflow. I have so many files to just "stamp" with an effect I designed last batch so this is great!

                                      1. Chad Wahlbrink @Chad2023-12-21 18:28:35.212Z

                                        I'm glad it worked for ya!

                                        Happy Holidays!

                                        1. DDane Butler @Dane_Butler
                                            2023-12-22 02:01:39.691Z

                                            Happy holidays!

                            • In reply toChad:
                              GGeorge Hinson @George_Hinson
                                2024-03-07 12:46:15.184Z

                                Hey @Chad thanks for this! I'm getting issues on line 100
                                'TypeError: Cannot read property 'memLocName' of undefined'

                                I wondered if I am selecting something incorrectly? Very new to this any advice would be great,
                                Thanks,
                                All the best,

                                George