No internet connection
  1. Home
  2. How to

Duplicate a highlighted clip and its track, rename track with B, C, D, etc

By Jonathan Grossman @Jonathan_Grossman
    2022-01-19 23:02:15.113Z

    I have this code to copy a clip on a track, dupe the track below the existing one, paste that clip and rename the track, alphabetically sequencing., Works great for the first track. But if I already have a "B" track and then go back to the original track to make another copy, ostensibly a “C”, it doesn’t know there is already a “B” track, stops the macro and says there is already a track by that name with a "B" suffix.

    Can anyone helpt to adjust the script so it choses the next available letter ?

    //This script duplicates a selected track (eg.Track 1), then renames the duplicate "Track 4B" then "Track 4C" etc.
    
    
    //Note: This should be assigned to a keyboard shortcut or Stream Deck button.
    
    
    
    sf.keyboard.press({
    
    keys: "b, cmd+c, cmd+m",
    
    });
    
    
    
    function getTrackName() {
    
    
    var selectedTrackName = sf.ui.proTools.selectedTrackNames[0];1
    
    
    }
    
    
    function renameTrack() {
    
    
    sf.ui.proTools.selectedTrack.trackOpenRenameDialog();
    
    
    }
    
    
    function duplicateSelectedTrack() {
    
    
    sf.ui.proTools.trackDuplicateSelected({
    
    
    duplicateActivePlaylist: false,
    
    
    duplicateAlternatePlaylists: true,
    
    
    duplicateAutomation: true,
    
    
    });
    
    
    }
    
    
    function main() {
    
    
    if (sf.ui.proTools.selectedTrackCount === 1) {
    
    
    //Get track name of selected track
    
    
    var originalTrackName = getTrackName();
    
    
    //Duplicate the selected track
    
    
    duplicateSelectedTrack();
    
    
    sf.ui.proTools.selectedTrack.trackRename({
    
    
    renameFunction: function (name) {
    
    
    var letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
    
    
    //This function gets called after the Rename track dialog is open
    
    
    //the argument 'name' holds the existing name, eg. "Audio B.dup1"
    
    
    //We're supposed to eventually return the target name we want from this function.
    
    
    var grps = name.match(/^(.*)([ \d])([a-zA-Z])(\.dup.*|)$/);
    
    
    //This regular expression matches a string containing of the following groups (each group is in parentheses)
    
    
    //Group 1 - syntax: (.*) - meaning: Match any number (*) of any characters (.) This will match "Audio"
    
    
    //Group 2 - syntax: ( ) - meaning: Match 1 space character
    
    
    //Group 3 - syntax: ([a-zA-Z]) - meaning: Match 1 character that is a letter. This will match "B"
    
    
    //After last group- syntax: \.dup.* - meaning: Match a dot (\.), the text "dup", and then ANY character (.), any number of times (*)
    
    
    //Save out the groups, baseName, space, numName
    
    
    var baseName, space, numName;
    
    
    if (grps) {
    
    
    baseName = grps[1];
    
    
    space = grps[2];
    
    
    numName = grps[3];
    
    
    } else {
    
    
    //It didn't match our format. Try again without the requirement of a suffix
    
    
    if (name.indexOf('.dup') >= 0)
    
    
    baseName = name.substring(0, name.indexOf('.dup')); //Get the base name before any .dup
    
    
    else
    
    
    baseName = name; 1
    
    
    space = " "; //default space
    
    
    numName = "";
    
    
    }
    
    
    //If you didn't have a number, by default we want a space ' ' before our new #2 track
    
    
    if (numName == "") space = "";
    
    
    //Create the new number. If there wasn't one, we'll default to 2, if there was one, convert our string to a number, and add 1 to that
    
    
    var newIndex = 1; //Start with 2nd index (1 because indices are always 0-based)
    
    
    if (numName) {
    
    
    var idx = letters.indexOf(numName.toUpperCase()); //Find the index (0-based)
    
    
    if (idx >= 0) {
    
    
    newIndex = idx + 1; //Set to the found index + 1
    
    
    }
    
    
    //If not found, default to 2nd index (1) which is already set
    
    
    }
    
    
    if (newIndex < 0 || newIndex >= letters.length)
    
    
    throw 'New index out of bounds'; //We reached Z or some other error
    
    
    var letter = letters[newIndex];
    
    
    if (numName && numName.toLowerCase() === numName)
    
    
    letter = letter.toLowerCase(); //Make letter lowercase if the input was lowercase
    
    
    //Return the new name, a combination of the base name "Audio", a space or not " ", and the new letter
    
    
    return baseName + space + letter;
    
    
    }
    
    
    });
    
    
    } else {
    
    
    log('More than one track is selected');
    
    
    //throw (0);
    
    
    }
    
    
    }
    
    
    main();
    
    
    sf.keyboard.press({
    
    keys: "cmd+v",
    
    });
    
    
    
    
    
    /////
    
    
    • 6 replies
    1. Raphael Sepulveda @raphaelsepulveda2022-01-20 08:26:50.158Z2022-01-21 05:50:37.127Z

      Hey @Jonathan_Grossman, nice script!

      Give this a try. Cleaned it up a bit but kept your commentary:

      //This script duplicates a selected track (eg.Track 1), then renames the duplicate "Track 4B" then "Track 4C" etc.
      //Note: This should be assigned to a keyboard shortcut or Stream Deck button.
      
      function duplicateSelectedTrack() {
          sf.ui.proTools.trackDuplicateSelected({
              duplicateActivePlaylist: false,
              duplicateAlternatePlaylists: true,
              duplicateAutomation: true,
          });
      }
      
      function separateClip() {
          sf.ui.proTools.menuClick({ menuPath: ['Edit', 'Separate Clip', 'At Selection'] });
      }
      
      function copyClip() {
          sf.ui.proTools.menuClick({ menuPath: ['Edit', 'Copy'] });
      }
      
      function pasteClip() {
          sf.ui.proTools.menuClick({ menuPath: ['Edit', 'Paste'] });
      }
      
      function muteClip() {
          sf.ui.proTools.menuClick({ menuPath: ['Edit', 'Mute Clips'] });
      }
      
      function unmuteClip() {
          sf.ui.proTools.menuClick({ menuPath: ['Edit', 'Unmute Clips'] });
      }
      
      function renameFunction(trackName) {
          //This function gets called after the Rename track dialog is open
          //the argument 'trackName' holds the existing name, eg. "Audio B.dup1"
          //We're supposed to eventually return the target name we want from this function.
      
          /** @param {{ name?: string, letter?: string, oldName?: string }} args */
          function getNewName({ name, letter, oldName }) {
              let newLetter;
      
              if (oldName) {
                  name = oldName.slice(0, -1);
                  newLetter = getNewLetter({ oldLetter: oldName.slice(-1) });
              } else {
                  newLetter = getNewLetter({ oldLetter: letter });
              }
      
              const newName = name + newLetter;
      
              return validateName(newName);
          }
      
          function getNewLetter({ oldLetter }) {
              const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
      
              if (!oldLetter) return letters[1];
      
              let oldLetterIndex = letters.indexOf(oldLetter.toUpperCase()); //Find the index (0-based)
              let newLetterIndex = ++oldLetterIndex; //Set to the found index + 1
      
              if (newLetterIndex >= letters.length) {
                  throw 'All letters have been used.'; //We reached Z or some other error
              }
      
              let newLetter = letters[newLetterIndex];
      
              if (oldLetter.toLowerCase() === oldLetter) {
                  newLetter = newLetter.toLowerCase(); //Make letter lowercase if the input was lowercase
              }
      
              return newLetter;
          }
      
          const validateName = function () {
              const namesOfAllTracksInSession = sf.ui.proTools.tracks.names;
      
              return (name) => {
                  function isThereATrackThatStartsWith(name) { // Prevents conflicts with playlist
                      return namesOfAllTracksInSession.some(trackName => trackName.startsWith(name))
                  }
      
                  if (isThereATrackThatStartsWith(name)) {
                      return getNewName({ oldName: name });
                  } else {
                      return name;
                  }
              }
          }();
      
          var grps = trackName.match(/^(.*)([ \d])([a-zA-Z])(\.dup.*)$/);
      
          //This regular expression matches a string containing of the following groups (each group is in parentheses)
          //Group 1 - syntax: (.*) - meaning: Match any number (*) of any characters (.) This will match "Audio"
          //Group 2 - syntax: ([ \d]) - meaning: Match a space ( ) or a number (\d)
          //Group 3 - syntax: ([a-zA-Z]) - meaning: Match 1 character that is a letter. This will match "B"
          //After last group- syntax: \.dup.* - meaning: Match a dot (\.), the text "dup", and then ANY character (.), any number of times (*)
      
          let baseName, spaceOrNumber, letter;
      
          if (grps) { // Match
              [, baseName, spaceOrNumber, letter] = grps; // Destructuring relevant group matches
          } else { // No Match
              baseName = trackName.substring(0, trackName.indexOf('.dup')).trim(); //Get the base name before any .dup
              spaceOrNumber = baseName.match(/.* \d+/) ? "" : " "; // If baseName ends in a space and a number return empty string, else return space.
          }
      
          const newName = getNewName({ name: baseName + spaceOrNumber, letter });
      
          return newName;
      }
      
      function duplicateAndRename() {
          duplicateSelectedTrack();
      
          sf.ui.proTools.selectedTrack.trackRename({
              renameFunction
          });
      }
      
      function main() {
          sf.ui.proTools.appActivateMainWindow();
          sf.ui.proTools.mainWindow.invalidate();
      
          if (sf.ui.proTools.selectedTrackCount !== 1) {
              throw 'Make sure that one track is selected.';
          }
      
          const isAClipSelected = sf.ui.proTools.getMenuItem('Edit', 'Trim Clip').isEnabled;
          const isSelectedClipMuted = sf.ui.proTools.getMenuItem('Edit', 'Unmute Clips').isEnabled;
      
          if (isAClipSelected) {
              isSelectedClipMuted ? unmuteClip() : separateClip();
              copyClip();
              muteClip();
          }
      
          duplicateAndRename();
      
          if (isAClipSelected) pasteClip();
      }
      
      main();
      
      1. J
        Jonathan Grossman @Jonathan_Grossman
          2022-01-20 17:03:02.982Z

          Thank you! This works almost perfectly! A tweak if I may ask....it fails if I reselect the now muted clip on the original track, in order to make another copy of the same thing (for alternate mix options).

          1. J
            Jonathan Grossman @Jonathan_Grossman
              2022-01-20 21:14:05.202Z

              Also just noticed that if I attempt this with a track that has a duplicated playlist with ".01" in the suffix, it fails as well.

              1. @Jonathan_Grossman, got em! Updated the script above.
                Give it another go!

                1. J
                  Jonathan Grossman @Jonathan_Grossman
                    2022-01-21 23:48:10.564Z

                    BAMMO!!! Thanks Raphael. Works perfectly now.