No internet connection
  1. Home
  2. How to

Smart Track Duplicator - Help With Script

By Philip weinrobe @Philip_weinrobe
    2024-08-19 17:09:24.505Z

    Hi There!
    @raphaelsepulveda had been talking about this process in an instagram post, but I have a script here that i've been using for a while to duplicate tracks in a smart way.

    @raphaelsepulveda this script stopped working at line 64 today (bulk rename) but instead of troubleshooting it too much i wondered if you had a better solution at this point!

    sf.ui.proTools.appActivate();
    sf.ui.proTools.invalidate();
    
    // Place the following two functions at the top of your script:
    
    // #1
    function mainWindowStatus() {
        if (sf.ui.proTools.getMenuItem('Window', 'Mix').isMenuChecked) {
            sf.ui.proTools.menuClick({
                    menuPath: ["Window", "Edit"],
                });
            return "Mix";
        } else {
            return "Edit";
        }
    }
    
    //#2
    function returnToStartingMainWIndow(mainWindow) {
        if (mainWindow =="Mix") {
                sf.ui.proTools.menuClick({
                    menuPath: ["Window", "Mix"],
                });
        }
    }
    
    
    /// Place this line at the top of your script
    let startingWindow = mainWindowStatus();
    
    
    
    function main() {
    
        sf.ui.proTools.appActivateMainWindow();
        sf.ui.proTools.invalidate();
    
        var numberOfDups = Number(prompt('How many duplicates?', '1'));
    
    
        let tracksToDup = sf.ui.proTools.selectedTrackNames;
    
        sf.ui.proTools.trackDuplicateSelected({
            duplicateActivePlaylist: false,
            duplicateAlternatePlaylists: false,
            duplicateAutomation: false,
            duplicateGroupAssignments: false,
            duplicateInserts: true,
            duplicateSends: true,
            insertAfterLastSelectedTrack: true,
            numberOfDuplicates: numberOfDups
        });
    
    
    
        const dupTracks = sf.ui.proTools.invalidate().selectedTrackNames;
    
        //Rename Tracks
        tracksToDup.forEach(trackName => {
    
            const trackDups = dupTracks.filter(tn => tn.startsWith(trackName))
    
            sf.ui.proTools.trackSelectByName({ names: [trackName, ...trackDups] });
            sf.ui.proTools.selectedTrack.trackBulkRenameNumerical();
    
        });
    
    };
    
    main();
    
    
    // end your script with this line
    returnToStartingMainWIndow(startingWindow);
    
    
    • 8 replies
    1. @Philip_weinrobe, yeah! We can definitely modernize this one by leveraging the PT SDK.
      We can now rename tracks directly so we don't need to do the Edit/Mix window switch-a-roo anymore.

      The renaming function from the script I showed on Instagram is custom to my naming scheme. All that means is that I have it rename tracks differently depending on what the original name is. For this script, I've recreated the trackBulkRenameNumerical function so we can use it with the PT SDK. It should work the same way you're used to. Let me know if it misbehaves!

      function duplicateTracksSmartRename() {
          /** Renames tracks by removing ".dup#" and numbering sequentially.
          * @param { string[] } trackNames */
          function trackBulkRenameNumerical(trackNames) {
              trackNames.forEach((trackName, index) => {
                  const newName = trackName
                      .replace(/.dup.*/, "") // Remove ".dup" and anything after that
                      .replace(/\s?\d+$/, "") // Remove numbers at end of name
                      + " " // Add space
                      + String(index + 1); // Add number
      
                  if (trackName === newName) return;
      
                  sf.app.proTools.renameTrack({
                      oldName: trackName,
                      newName
                  })
              });
          }
      
          const numberOfDups = Number(
              sf.interaction.displayDialog({
                  title: "Duplicate Tracks (Smart Rename)",
                  prompt: "How many duplicates?",
                  defaultAnswer: "1",
                  buttons: ["Cancel", "Ok"],
                  defaultButton: "Ok",
                  cancelButton: "Cancel",
              }).text
          );
      
          sf.ui.proTools.appActivateMainWindow();
          sf.ui.proTools.mainWindow.invalidate();
      
          const tracksToDup = sf.ui.proTools.selectedTrackNames;
      
          // Duplicate tracks
          sf.ui.proTools.trackDuplicateSelected({
              duplicateActivePlaylist: false,
              duplicateAlternatePlaylists: false,
              duplicateAutomation: false,
              duplicateGroupAssignments: false,
              duplicateInserts: true,
              duplicateSends: true,
              insertAfterLastSelectedTrack: true,
              numberOfDuplicates: numberOfDups
          });
      
          sf.ui.proTools.mainWindow.invalidate();
      
          const dupTracks = sf.ui.proTools.selectedTrackNames;
      
          //Rename Tracks
          tracksToDup.forEach(trackName => {
              const trackDups = dupTracks.filter(tn => tn.startsWith(trackName))
              trackBulkRenameNumerical([trackName, ...trackDups]);
          });
      }
      
      duplicateTracksSmartRename();
      
      1. PPhilip weinrobe @Philip_weinrobe
          2024-08-20 09:13:40.556Z

          amazing! seems to work great from the jump.
          thanks raphael!

          1. PPhilip weinrobe @Philip_weinrobe
              2024-08-20 09:15:01.221Z

              actually, one small thing:
              my old script and this new one both have one inherent flaw: they don't know if a track name already exists in the session. i seem to recall you had solved that in your flow.
              any chance of including that error checking here?

              thanks!
              Philip

              1. Yeah, no problem. I'll take care of this over the weekend!

                1. PPhilip weinrobe @Philip_weinrobe
                    2024-09-27 14:37:21.429Z

                    fyi this stopped working?
                    here's some error code i got

                    27.09.2024 10:36:35.86 <info> [Backend]: !! Command Error: Duplicate Tracks [user:default:cl3ylhnjk0002ie10j6w7ykd0]:
                    Could not find running app with bundle id: 'com.avid.ProTools' (Duplicate Tracks: Line 32)
                    
                    << Command: Duplicate Tracks [user:default:cl3ylhnjk0002ie10j6w7ykd0]
                    
                    27.09.2024 10:36:36.02 <info> [Backend]: [KeyboardInterfaceController] Invalidating PT memlocs due to numpad enter
                    
                    27.09.2024 10:36:37.27 <info> [EditorWindow:Renderer]: Active Focus Container:  commandsPage Line 33963 file:///Applications/SoundFlow.app/Contents/Helpers/SoundFlow.app/Contents/Resources/app.asar/dist/editor.js
                    
                    1. At sf.ui.proTools.appActivateMainWindow()? Try this.

                      • Restart SoundFlow
                      • Make sure nothing is covering the top left corner of the edit window (where the traffic lights are). That will throw an error with appActivateMainWindow every time.
                      • If it's still cranky, replace it with sf.ui.proTools.appActivate()
                      1. AAdam Lilienfeldt @Adam_Lilienfeldt
                          2025-07-10 09:10:15.816Z

                          Hi Phil and Raphael,

                          I stumbled upon this post and decided to update your script and modify it to work with my naming conventions.
                          Maybe you'll find a use for it - it should be quite easy to modify too. I still need to do some testing on it, but so far it seems to work quite well.

                          The script gets all track names from the session and finds the correct numbering for the new track(s).
                          It also accounts for playlist numbering (I tend to name my tracks track name.01 from the get go). So if you duplicate a track with playlist numbering, it will add .01 to that track name.

                          If you have tracks called Guitar 1, Guitar 2, Guitar 3, Guitar 4 and decide to duplicate Guitar 1, it will call that track Guitar 5.
                          Same logic applies with playlist numbering - if you have Guitar 1.05, Guitar 2.01, Guitar 3.11 and duplicate Guitar 1.05, it becomes Guitar 4.01.

                          function duplicateTracksSmartRename() {
                              const numberOfDups = Number(
                                  sf.interaction.displayDialog({
                                      title: "Duplicate Tracks (Smart Rename)",
                                      prompt: "How many duplicates?",
                                      defaultAnswer: "1",
                                      buttons: ["Cancel", "Ok"],
                                      defaultButton: "Ok",
                                      cancelButton: "Cancel",
                                  }).text
                              );
                          
                              sf.ui.proTools.appActivateMainWindow();
                              sf.ui.proTools.mainWindow.invalidate();
                          
                              const tracksToDup = sf.ui.proTools.selectedTrackNames;
                          
                              // Duplicate tracks
                              sf.ui.proTools.trackDuplicateSelected({
                                  duplicateActivePlaylist: false,
                                  duplicateAlternatePlaylists: false,
                                  duplicateAutomation: false,
                                  duplicateGroupAssignments: false,
                                  duplicateInserts: true,
                                  duplicateSends: true,
                                  insertAfterLastSelectedTrack: true,
                                  numberOfDuplicates: numberOfDups
                              });
                          
                              sf.ui.proTools.mainWindow.invalidate();
                          
                              // Get the currently selected tracks (these are the newly created duplicates)
                              const newlyCreatedTracks = sf.ui.proTools.selectedTrackNames;
                              
                              //log(`Original tracks to duplicate: ${tracksToDup.join(', ')}`);
                              //log(`Newly created tracks (selected): ${newlyCreatedTracks.join(', ')}`);
                          
                              // Get ALL tracks in the session to check for existing numbers
                              const allTracksInSession = sf.ui.proTools.trackGetAllTracks().names;
                              
                              //log(`All tracks in session: ${allTracksInSession.join(', ')}`);
                          
                              // Keep track of numbers we've already assigned to avoid conflicts
                              const assignedNumbers = {};
                              
                              // Rename only the newly created duplicate tracks
                              tracksToDup.forEach((originalTrackName, originalIndex) => {
                                  //log(`Processing original track: ${originalTrackName}`);
                                  
                                  // Check if the original track has playlist numbering (.XX format)
                                  const hasPlaylistNumber = /\.\d{2}$/.test(originalTrackName);
                                  //log(`Original track has playlist numbering: ${hasPlaylistNumber}`);
                                  
                                  // Get the base name from the original track
                                  let baseName = originalTrackName.replace(/\.\d{2}$/, "").replace(/\s?\d+$/, "");
                                  
                                  //log(`Base name: ${baseName}`);
                                  
                                  // Find all existing track numbers for this base name in the entire session
                                  const existingNumbers = allTracksInSession
                                      .filter(trackName => {
                                          // Get the base name of this track
                                          const trackBaseName = trackName.replace(/\.dup\d+/, "").replace(/\.\d{2}$/, "").replace(/\s?\d+$/, "");
                                          return trackBaseName === baseName;
                                      })
                                      .map(trackName => {
                                          // Extract the number from the track name (after removing .dup and playlist)
                                          const withoutDupAndPlaylist = trackName.replace(/\.dup\d+/, "").replace(/\.\d{2}$/, "");
                                          const numberMatch = withoutDupAndPlaylist.match(/\s?(\d+)$/);
                                          return numberMatch ? parseInt(numberMatch[1]) : 1;
                                      });
                                  
                                  // Include previously assigned numbers from this session
                                  const allUsedNumbers = [...existingNumbers, ...(assignedNumbers[baseName] || [])];
                                  const highestExistingNumber = allUsedNumbers.length > 0 ? Math.max(...allUsedNumbers) : 0;
                                  
                                  //log(`Existing numbers for ${baseName}: ${existingNumbers.join(', ')}`);
                                  //log(`Previously assigned numbers: ${(assignedNumbers[baseName] || []).join(', ')}`);
                                  //log(`Highest existing number: ${highestExistingNumber}`);
                                  
                                  // Initialize the assigned numbers array for this base name if it doesn't exist
                                  if (!assignedNumbers[baseName]) {
                                      assignedNumbers[baseName] = [];
                                  }
                                  
                                  // Find the duplicates that were created from this specific original track
                                  const duplicatesFromThisTrack = newlyCreatedTracks.filter(trackName => {
                                      // Check if this duplicate was created from the current original track
                                      // Remove .dup# but keep the playlist number
                                      const withoutDup = trackName.replace(/\.dup\d+/, "");
                                      const hasDup = trackName.includes('.dup');
                                      const matches = withoutDup === originalTrackName;
                                      
                                      //log(`Checking track: "${trackName}"`);
                                      //log(`  Without dup: "${withoutDup}"`);
                                      //log(`  Has .dup: ${hasDup}`);
                                      //log(`  Matches original: ${matches}`);
                                      //log(`  Result: ${hasDup && matches}`);
                                      
                                      return hasDup && matches;
                                  });
                                  
                                  //log(`Found ${duplicatesFromThisTrack.length} duplicates: ${duplicatesFromThisTrack.join(', ')}`);
                                  
                                  // Rename each duplicate, starting from the next available number
                                  duplicatesFromThisTrack.forEach((dupTrackName, dupIndex) => {
                                      const newNumber = highestExistingNumber + dupIndex + 1;
                                      
                                      // Track this number as assigned
                                      assignedNumbers[baseName].push(newNumber);
                                      
                                      // Only add playlist numbering if the original track had it
                                      const newName = hasPlaylistNumber 
                                          ? baseName + " " + newNumber + ".01"
                                          : baseName + " " + newNumber;
                                      
                                      //log(`Renaming: "${dupTrackName}" -> "${newName}"`);
                                      
                                      if (dupTrackName === newName) {
                                          log("Names are identical, skipping rename");
                                          return;
                                      }
                                      
                                      sf.app.proTools.renameTrack({
                                          oldName: dupTrackName,
                                          newName: newName
                                      });
                                  });
                              });
                          }
                          
                          duplicateTracksSmartRename();