No internet connection
  1. Home
  2. Script Sharing

Splitting tracks into mono

By Christian Scheuer @chrscheuer2025-01-22 02:39:58.511Z2025-01-23 05:10:28.640Z

This is the final script for the video:


sf.app.proTools.tracks.invalidate();

// Get stereo audio tracks
var stereoTracks = sf.app.proTools.tracks.allItems.filter(t => t.format === 'TFStereo' && t.type === 'Audio');

// Get their names
var stereoTrackNames = stereoTracks.map(t => t.name);

// Make sure they're selected
sf.app.proTools.selectTracksByName({
    selectionMode: 'Replace',
    trackNames: stereoTrackNames,
});

// Click the menu item "Track" -> "Split into Mono"
sf.ui.proTools.menuClick({
    menuPath: ['Track', 'Split into Mono'],
});

// Select the original stereo tracks
sf.app.proTools.selectTracksByName({
    selectionMode: 'Replace',
    trackNames: stereoTrackNames,
});

// Call "Track" -> "Delete..."
sf.ui.proTools.menuClick({
    menuPath: ['Track', 'Delete...'],
});

// Wait for the confirmation dialog to appear
sf.ui.proTools.confirmationButtonDialog.elementWaitFor();

// Click "Delete"
sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Delete").first.elementClick();

  • 26 replies

There are 26 replies. Estimated reading time: 47 minutes

  1. Christian Scheuer @chrscheuer2025-01-23 04:56:54.799Z2025-01-23 05:10:12.644Z

    Here's the evolution of the script from part 2 of the video:

    It now analyses the audio content on all stereo audio tracks in the session, and only splits those tracks with identical content on the left and right channels.
    It also ensures to delete one of the split tracks (no need to keep both the left and right channels if they're identical).

    
    function areChannelsIdentical(audioFilePath) {
        var commandLine = `ffmpeg -i "${audioFilePath}" -filter_complex "[0:a]channelsplit=channel_layout=stereo[left][right]; [left][right]amix=inputs=2:weights='1 -1'[diff]; [diff]astats=metadata=1" -f null - 2>&1 | grep "Max level"`;
    
        var ffmpegResult = sf.system.exec({
            commandLine: commandLine,
        }).result;
    
        var maxLevel = Number(ffmpegResult.match(/Max level: ([\d\.]+)/)[1]);
    
        if (maxLevel <= 0.0001) {
            //log(`Left and Right are identical`);
            return true;
        } else {
            //log(`Left and Right are different`);
            return false;
        }
    }
    
    function splitTracksIntoMonoAndDeleteOriginals(trackNames) {
        // Make sure the tracks in question are selected
        sf.app.proTools.selectTracksByName({
            selectionMode: 'Replace',
            trackNames: trackNames,
        });
    
        // 
        var tracksToDelete = trackNames.slice();
    
        // Remember all tracks in the session
        var trackNamesBefore = sf.app.proTools.tracks.invalidate().allItems.map(t => t.name);
    
        // Click the menu item "Track" -> "Split into Mono"
        sf.ui.proTools.menuClick({
            menuPath: ['Track', 'Split into Mono'],
        });
    
        alert('Click OK when all tracks are split');
    
        var trackNamesAfter = sf.app.proTools.tracks.invalidate().allItems.map(t => t.name);
    
        // Let's get the names of all the newly added tracks
        var newlyCreatedTrackNames = trackNamesAfter.filter(t => !trackNamesBefore.some(b => t === b));
    
        for (var i = 0; i < newlyCreatedTrackNames.length; i++) {
            if (i % 2 === 1) {
                tracksToDelete.push(newlyCreatedTrackNames[i]);
            }
        }
    
        // Select the tracks to delete
        sf.app.proTools.selectTracksByName({
            selectionMode: 'Replace',
            trackNames: tracksToDelete,
        });
    
        // Call "Track" -> "Delete..."
        sf.ui.proTools.menuClick({
            menuPath: ['Track', 'Delete...'],
        });
    
        // Wait for the confirmation dialog to appear
        sf.ui.proTools.confirmationButtonDialog.elementWaitFor();
    
        // Click "Delete"
        sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Delete").first.elementClick();
    }
    
    function getAudioFilePathOfFirstClipOnTrack(trackName) {
        sf.app.proTools.selectTracksByName({
            trackNames: [trackName],
            selectionMode: 'Replace',
        });
    
        sf.keyboard.press({
            keys: "return, shift+quote",
        });
    
        var audioFilePath = sf.app.proTools.getFileLocation({
            fileFilters: ['SelectedClipsTimeline'],
        }).fileLocations[0].path;
    
        return audioFilePath;
    }
    
    function main() {
    
        // Invalidate the PT tracks (in the PT SDK part of SoundFlow)
        sf.app.proTools.tracks.invalidate();
    
        // Get stereo audio tracks
        var stereoTracks = sf.app.proTools.tracks.allItems.filter(t => t.format === 'TFStereo' && t.type === 'Audio');
    
        // Get their names
        var stereoTrackNames = stereoTracks.map(t => t.name);
    
        // Figure out, which of those stereo tracks should be processed
        // We want to only process those tracks, where the first clip of the track
        // contains identical content on the left and right channels
        var trackNamesToProcess = [];
        for (let stereoTrackName of stereoTrackNames) {
            var audioFilePath = getAudioFilePathOfFirstClipOnTrack(stereoTrackName);
    
            if (areChannelsIdentical(audioFilePath)) {
                trackNamesToProcess.push(stereoTrackName);
            }
        }
    
        // Clear the timeline selection
        sf.keyboard.press({ keys: 'return' });
    
        // Select the tracks we want to process
        sf.app.proTools.selectTracksByName({
            selectionMode: 'Replace',
            trackNames: trackNamesToProcess,
        });
    
        // Split the tracks and delete the originals
        splitTracksIntoMonoAndDeleteOriginals(trackNamesToProcess);
    
    }
    
    main();
    
    1. Ddanielkassulke @danielkassulke
        2025-01-23 05:36:09.085Z2025-01-23 07:00:27.369Z

        ooooohhh very good. Circling back after testing - and bear in mind the scripting in this is so far over my head - this line: var maxLevel = Number(ffmpegResult.match(/Max level: ([\d\.]+)/)[1]); throws the error TypeError: Cannot read property '1' of null consistently. Fully paired back, this snippet:

        var audioFilePath = sf.app.proTools.getFileLocation({
            fileFilters: ["SelectedClipsTimeline"],
        }).fileLocations[0].path;
        
        var commandLine = `ffmpeg -i "${audioFilePath}" -filter_complex "[0:a]channelsplit=channel_layout=stereo[left][right]; [left][right]amix=inputs=2:weights='1 -1'[diff]; [diff]astats=metadata=1" -f null - 2>&1 | grep "Max level"`;
        
        var ffmpegResult = sf.system.exec({
            commandLine: commandLine,
        }).result;
        
        log(ffmpegResult);
        

        logs an empty array for me on both a laptop running Sequoia 15.2 and a Mac Studio running Ventura 13.6.7 with the newest version of ffmpeg installed on both. Any thoughts?

        1. Try removing the "| grep..." part (everything from the pipe and to the right), to see what you get result-wise when not using grep to only take part of the result.

          1. I wonder if the problem is that some installations of ffmpeg don't include the necessary filters to make this happen.
            Also, please be sure that you only test this with an interleaved stereo file as the input, since that's what that ffmpeg code expects.

        2. In reply tochrscheuer:
          SSreejesh Nair @Sreejesh_Nair
            2025-01-23 07:09:10.746Z

            I had a suggestion. On line 75, it might be better to use

            sf.keyboard.press({
                keys: "return, ctrl+tab",
            });
            

            because shift + quote will select a space if it exists before the clip starts.

            1. Great catch!

            2. In reply tochrscheuer:
              AAdam Lilienfeldt @Adam_Lilienfeldt
                2025-01-23 11:00:29.393Z

                Hi Christian,

                When running the script I get this error:

                TypeError: Cannot read property 'path' of undefined
                (Split stereo track into mono line 79) 
                

                Any ideas to why that is? Getting this to work would be incredible!
                Thanks for your engagement!
                Adam

                1. AAdam Lilienfeldt @Adam_Lilienfeldt
                    2025-01-28 11:11:34.005Z

                    Hi @chrscheuer
                    ffmpeg seems to work perfectly for me now, but I'm getting this issue as stated above at line 79.
                    Do you have any ideas at so where this is coming from?

                    Tusind tak herfra!

                    1. Hi Adam,

                      Fun to see a fellow Dane in here :)

                      That error indicates that there's no clip selected (or, no "full clip") at the time that line of code is run.
                      The script currently expects there to be a clip right at the start of the session on each track to work.

                2. J
                  In reply tochrscheuer:
                  julien creu @julien_creu
                    2025-01-26 04:31:53.861Z

                    Hi @chrscheuer !
                    Thanks a lot for this script idea which would be a real time savior! As for info, same issue is happening here as @danielkassulke and @Adam_Lilienfeldt mentionned.
                    Testing without the "| grep..." part didn’t help so far. As for ffmpeg, last version should be installed (via homebrew as far as I’m concerned).
                    Running Ventura :)

                    1. Thanks Julien. I will need to see the result you are getting when running the command without the grep part, in order to understand what's going on. Removing the grep is not a solution, it's a troubleshooting step to try to know more about what's going on under the hood on your side. Since I can't reproduce that behavior here, I'm dependent on seeing the results - please see my comment here:

                      1. Jjulien creu @julien_creu
                          2025-01-27 10:36:33.410Z

                          Hi @chrscheuer ,

                          Here's the log without the grep part.

                          27.01.2025 11:32:44.89 <info> [Backend]: NSArray.ArrayFromHandle count = 1
                          
                          27.01.2025 11:32:45.17 <info> [Backend]: [LOG] /bin/bash: line 5: ffmpeg: command not found
                          
                          27.01.2025 11:32:45.32 <info> [Backend]: JavaScript error with InnerException: null
                          !! Command Error: JC_FAKE STEREO CHECK [user:clm7l6vo500014110cqs75wbv:cm69bcg940000s510he7rsxfc]:
                          TypeError: Cannot read property '1' of null
                          (JC_FAKE STEREO CHECK line 10)
                          

                          As for information, here's a screenshot taken from the terminal, with the ffmpeg versions and dependencies that are installed on my side. Hope that helps :)

                          1. Oh, I see. Ok, so the problem here is that ffmpeg isn't found.

                            You'll need to replace ffmpeg in the commandLine with the full path to where ffmpeg is stored.

                            • First, type which ffmpeg in Terminal. This displays the full path to the binary. It could be for example something like /opt/homebrew/Cellar/ffmpeg/......./ffmpeg. Copy this full path to clipboard.

                            • Now, in this line, make sure to put the full path to the ffmpeg executable:

                            var commandLine = `/opt/homebrew/.../.../.../ffmpeg -i "${audioFilePath}" -filter_complex "[0:a]channelsplit=channel_layout=stereo[left][right]; [left][right]amix=inputs=2:weights='1 -1'[diff]; [diff]astats=metadata=1" -f null - 2>&1 | grep "Max level"`;
                            
                            1. Jjulien creu @julien_creu
                                2025-01-27 17:10:34.641Z

                                Hi @chrscheuer ! Thanks a lot for your feedback! Indeed that worked on my side, no more errors and the script is running and working like a charm!!
                                Will definitely be one of the most used one :)

                                One quick question/suggestion that raised while using it: if a mono track has been panned in ableton, it will still be exported as a a stereo track, having the exact same audio on left and right, but only with a different gain/levels. Would there be a way for this script to also detect those kind of tracks that only have gain differences between the two channels? the extreme case of that beeing hard panned tracks such as those ones:

                                I assume it would be convenient to automatically keep the left channel on the OHL and right channel on the OHR in this specific case :)

                                1. Great to hear it got solved with the path fix!

                                  Oh, that's an interesting problem. I ran out of time looking into this particular problem due to NAMM, so it might be a while before I can take a look again, but the biggest challenge would probably be doing the null-sum with two tracks that are not of identical gain. I can't think of an easy solution right on the spot, but I'll keep thinking

                                  1. Jjulien creu @julien_creu
                                      2025-02-03 13:06:44.336Z

                                      Hi @chrscheuer !
                                      Hope you're doing good!
                                      I was trying to implement the part where the script also detects when tracks are "left only" or "right only" (meaning one of the two channels is silent).
                                      I started with something like this

                                      function isLeftChannelOnly(audioFilePath) {
                                          var commandLine = `/opt/homebrew/bin/ffmpeg -i "${audioFilePath}" -af "channelsplit=channel_layout=stereo[left][right],astats=metadata=1:reset=1" -f null - 2>&1 | grep "Channel 1, Max level"`; 
                                          var ffmpegResult = sf.system.exec({
                                              commandLine: commandLine,
                                          }).result;
                                      
                                          var rightMaxLevelMatch = Number(ffmpegResult.match(/Max level: ([\d\.]+)/)[1]);
                                          
                                          if (!rightMaxLevelMatch) {
                                              throw new Error("Impossible d'extraire le niveau max du canal droit.");
                                          }
                                      
                                          var rightMaxLevel = Number(rightMaxLevelMatch[1]);
                                      
                                          return rightMaxLevel <= 0.0001; // threshold where right channel would be considered as silent
                                      }
                                      

                                      But it seem that the ffmpeg command doesn't work the way I expected... Would you have any clue on how to make this one work?
                                      Thanks in advance!!!

                                      1. SSreejesh Nair @Sreejesh_Nair
                                          2025-02-03 13:50:47.956Z

                                          An idea would be use the same method to find if it’s mono and do a strip silence on the two split tracks. If there was audio in only one track, the other would be empty. You could side then delete the track that’s empty. If both have signals then you know that they are the same since it’s a result of the ffmpeg saying it’s mono and you can delete one of them as Christian showed.

                                          1. Jjulien creu @julien_creu
                                              2025-02-03 15:56:34.009Z

                                              Hi @Sreejesh_Nair !
                                              Thanks for this idea, I assume that this could work as a workaround, but there are different things I'd like to automate at some point, such as panning the remaining mono track left (or right) depending if the original track is a left or right only stereo track. That's why I thought of this function to "detect" in which scenario we're in, and depending on the result, automate the whole process of keeping the good track and panning it in the right place (which would be a lil bit more complicated if I go through the strip silence part you're suggesting). All those steps are actually almost done with the snippets added by @danielkassulke and @Adam_Lilienfeldt in this thread, and the whole detection and splitting are also almost done in @chrscheuer script, but I'm struggling with the ffmpeg command to use in order to keep the logic that was used in the original "areChannelsIdentical()" function, but only "detect" those empty channels
                                              :)

                                      2. In reply tochrscheuer:
                                        Ddanielkassulke @danielkassulke
                                          2025-01-28 06:18:16.158Z

                                          This resolved my issue too, thanks!

                                          I added a snippet to wipe the .L suffix at the end:

                                          sf.ui.proTools.appActivateMainWindow();
                                          
                                          function areChannelsIdentical(audioFilePath) {
                                              var commandLine = `/opt/homebrew/bin/ffmpeg -i "${audioFilePath}" -filter_complex "[0:a]channelsplit=channel_layout=stereo[left][right]; [left][right]amix=inputs=2:weights='1 -1'[diff]; [diff]astats=metadata=1" -f null - 2>&1 | grep "Max level"`;
                                          
                                              var ffmpegResult = sf.system.exec({
                                                  commandLine: commandLine,
                                              }).result;
                                          
                                              var maxLevel = Number(ffmpegResult.match(/Max level: ([\d\.]+)/)[1]);
                                          
                                              if (maxLevel <= 0.0001) {
                                                  return true;
                                              } else {
                                                  return false;
                                              }
                                          }
                                          
                                          function splitTracksIntoMonoAndDeleteOriginals(trackNames) {
                                              sf.app.proTools.selectTracksByName({
                                                  selectionMode: 'Replace',
                                                  trackNames: trackNames,
                                              });
                                          
                                              var tracksToDelete = trackNames.slice();
                                          
                                              var trackNamesBefore = sf.app.proTools.tracks.invalidate().allItems.map(t => t.name);
                                          
                                              sf.ui.proTools.menuClick({
                                                  menuPath: ['Track', 'Split into Mono'],
                                              });
                                          
                                              alert('Click OK when all tracks are split');
                                          
                                              var trackNamesAfter = sf.app.proTools.tracks.invalidate().allItems.map(t => t.name);
                                          
                                              var newlyCreatedTrackNames = trackNamesAfter.filter(t => !trackNamesBefore.some(b => t === b));
                                          
                                              for (var i = 0; i < newlyCreatedTrackNames.length; i++) {
                                                  if (i % 2 === 1) {
                                                      tracksToDelete.push(newlyCreatedTrackNames[i]);
                                                  }
                                              }
                                          
                                              sf.app.proTools.selectTracksByName({
                                                  selectionMode: 'Replace',
                                                  trackNames: tracksToDelete,
                                              });
                                          
                                              sf.ui.proTools.menuClick({
                                                  menuPath: ['Track', 'Delete...'],
                                              });
                                          
                                              sf.ui.proTools.confirmationButtonDialog.elementWaitFor();
                                          
                                              sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Delete").first.elementClick();
                                          
                                              return newlyCreatedTrackNames.filter(name => name.endsWith('.L'));
                                          }
                                          
                                          function renameTracksWithoutSuffix(trackNames) {
                                              sf.app.proTools.selectTracksByName({
                                                  selectionMode: 'Replace',
                                                  trackNames: trackNames,
                                              });
                                          
                                              for (let trackName of trackNames) {
                                                  if (trackName.endsWith('.L')) {
                                                      var newName = trackName.replace(/\.L$/, '');
                                                      sf.app.proTools.renameTrack({
                                                          oldName: trackName,
                                                          newName: newName,
                                                      });
                                                  }
                                              }
                                          }
                                          
                                          function getAudioFilePathOfFirstClipOnTrack(trackName) {
                                              sf.app.proTools.selectTracksByName({
                                                  trackNames: [trackName],
                                                  selectionMode: 'Replace',
                                              });
                                          
                                              sf.keyboard.press({
                                                  keys: "return, shift+quote",
                                              });
                                          
                                              var audioFilePath = sf.app.proTools.getFileLocation({
                                                  fileFilters: ['SelectedClipsTimeline'],
                                              }).fileLocations[0].path;
                                          
                                              return audioFilePath;
                                          }
                                          
                                          function main() {
                                              sf.app.proTools.tracks.invalidate();
                                          
                                              var stereoTracks = sf.app.proTools.tracks.allItems.filter(t => t.format === 'TFStereo' && t.type === 'Audio');
                                          
                                              var stereoTrackNames = stereoTracks.map(t => t.name);
                                          
                                              var trackNamesToProcess = [];
                                              for (let stereoTrackName of stereoTrackNames) {
                                                  var audioFilePath = getAudioFilePathOfFirstClipOnTrack(stereoTrackName);
                                          
                                                  if (areChannelsIdentical(audioFilePath)) {
                                                      trackNamesToProcess.push(stereoTrackName);
                                                  }
                                              }
                                          
                                              sf.keyboard.press({ keys: 'return' });
                                          
                                              sf.app.proTools.selectTracksByName({
                                                  selectionMode: 'Replace',
                                                  trackNames: trackNamesToProcess,
                                              });
                                          
                                              var newlyCreatedMonoTracks = splitTracksIntoMonoAndDeleteOriginals(trackNamesToProcess);
                                          
                                              renameTracksWithoutSuffix(newlyCreatedMonoTracks);
                                          }
                                          
                                          main();
                                          
                                          1. Nice!!

                                            1. In reply todanielkassulke:
                                              AAdam Lilienfeldt @Adam_Lilienfeldt
                                                2025-01-30 10:30:24.881Z

                                                I've got this up an running now too!

                                                I also added a function to pan all the newly created mono tracks to center:

                                                sf.ui.proTools.appActivateMainWindow();
                                                
                                                function areChannelsIdentical(audioFilePath) {
                                                        var commandLine = `ffmpeg -i "${audioFilePath}" -filter_complex "[0:a]channelsplit=channel_layout=stereo[left][right]; [left][right]amix=inputs=2:weights='1 -1'[diff]; [diff]astats=metadata=1" -f null - 2>&1 | grep "Max level"`;
                                                
                                                
                                                    var ffmpegResult = sf.system.exec({
                                                        commandLine: commandLine,
                                                    }).result;
                                                
                                                    var maxLevel = Number(ffmpegResult.match(/Max level: ([\d\.]+)/)[1]);
                                                
                                                    if (maxLevel <= 0.0001) {
                                                        return true;
                                                    } else {
                                                        return false;
                                                    }
                                                }
                                                
                                                function splitTracksIntoMonoAndDeleteOriginals(trackNames) {
                                                    sf.app.proTools.selectTracksByName({
                                                        selectionMode: 'Replace',
                                                        trackNames: trackNames,
                                                    });
                                                
                                                    var tracksToDelete = trackNames.slice();
                                                
                                                    var trackNamesBefore = sf.app.proTools.tracks.invalidate().allItems.map(t => t.name);
                                                
                                                    sf.ui.proTools.menuClick({
                                                        menuPath: ['Track', 'Split into Mono'],
                                                    });
                                                
                                                    alert('Click OK when all tracks are split');
                                                
                                                    var trackNamesAfter = sf.app.proTools.tracks.invalidate().allItems.map(t => t.name);
                                                
                                                    var newlyCreatedTrackNames = trackNamesAfter.filter(t => !trackNamesBefore.some(b => t === b));
                                                
                                                    for (var i = 0; i < newlyCreatedTrackNames.length; i++) {
                                                        if (i % 2 === 1) {
                                                            tracksToDelete.push(newlyCreatedTrackNames[i]);
                                                        }
                                                    }
                                                
                                                    sf.app.proTools.selectTracksByName({
                                                        selectionMode: 'Replace',
                                                        trackNames: tracksToDelete,
                                                    });
                                                
                                                    sf.ui.proTools.menuClick({
                                                        menuPath: ['Track', 'Delete...'],
                                                    });
                                                
                                                    sf.ui.proTools.confirmationButtonDialog.elementWaitFor();
                                                
                                                    sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Delete").first.elementClick();
                                                
                                                    return newlyCreatedTrackNames.filter(name => name.endsWith('.L'));
                                                }
                                                
                                                function renameTracksWithoutSuffix(trackNames) {
                                                    sf.app.proTools.selectTracksByName({
                                                        selectionMode: 'Replace',
                                                        trackNames: trackNames,
                                                    });
                                                
                                                    for (let trackName of trackNames) {
                                                        if (trackName.endsWith('.L')) {
                                                            var newName = trackName.replace(/\.L$/, '');
                                                            sf.app.proTools.renameTrack({
                                                                oldName: trackName,
                                                                newName: newName,
                                                            });
                                                        }
                                                    }
                                                }
                                                
                                                function panTracksToCenter(trackNames) {
                                                    sf.app.proTools.selectTracksByName({
                                                        selectionMode: 'Replace',
                                                        trackNames: trackNames,
                                                    });
                                                
                                                    //Pan new mono tracks to the center
                                                    sf.ui.proTools.selectedTrackHeaders.forEach(track => {
                                                        track.groups.whoseTitle.is("Audio IO").first.sliders.whoseTitle.startsWith("Audio Pan indicator").first.mouseClickElement({
                                                            relativePosition: { "x": 3, "y": 3 },
                                                            isOption: true,
                                                        });
                                                    });
                                                }
                                                
                                                function getAudioFilePathOfFirstClipOnTrack(trackName) {
                                                    sf.app.proTools.selectTracksByName({
                                                        trackNames: [trackName],
                                                        selectionMode: 'Replace',
                                                    });
                                                
                                                    sf.keyboard.press({
                                                        keys: "return, shift+quote",
                                                    });
                                                
                                                    var audioFilePath = sf.app.proTools.getFileLocation({
                                                        fileFilters: ['SelectedClipsTimeline'],
                                                    }).fileLocations[0].path;
                                                
                                                    return audioFilePath;
                                                }
                                                
                                                function main() {
                                                    sf.app.proTools.tracks.invalidate();
                                                
                                                    var stereoTracks = sf.app.proTools.tracks.allItems.filter(t => t.format === 'TFStereo' && t.type === 'Audio');
                                                
                                                    var stereoTrackNames = stereoTracks.map(t => t.name);
                                                
                                                    var trackNamesToProcess = [];
                                                    for (let stereoTrackName of stereoTrackNames) {
                                                        var audioFilePath = getAudioFilePathOfFirstClipOnTrack(stereoTrackName);
                                                
                                                        if (areChannelsIdentical(audioFilePath)) {
                                                            trackNamesToProcess.push(stereoTrackName);
                                                        }
                                                    }
                                                
                                                    sf.keyboard.press({ keys: 'return' });
                                                
                                                    sf.app.proTools.selectTracksByName({
                                                        selectionMode: 'Replace',
                                                        trackNames: trackNamesToProcess,
                                                    });
                                                
                                                    var newlyCreatedMonoTracks = splitTracksIntoMonoAndDeleteOriginals(trackNamesToProcess);
                                                
                                                    panTracksToCenter(newlyCreatedMonoTracks);
                                                
                                                    renameTracksWithoutSuffix(newlyCreatedMonoTracks);
                                                
                                                }
                                                
                                                main();
                                                
                                      3. S
                                        In reply tochrscheuer:
                                        Sreejesh Nair @Sreejesh_Nair
                                          2025-02-03 19:16:35.888Z2025-02-04 06:27:33.330Z

                                          Hi @julien_creu! Here is a code that combines everything above with the beautiful code that @danielkassulke and @Adam_Lilienfeldt wrote. I additionally just changed the panning method to be done in one go rather than each track. The code detects if the signal is Mono, Left, Right or Stereo, splits the relevant tracks, deletes the tracks not needed, renames and pans the remaining new ones to the center.

                                          I used @chrscheuer 's idea of the channel weightage. If the result of summing 1 and -1 are zero then its mono. If the sum of weightage of 1 0 has signal and 0 1 doesn't, it is a left channel. If the sum of weightage of 1 0 doesn't have a signal and 0 1 has a signal, then its a right channel. If none of these are the conditions then it is stereo. Using this, and mapping the tracks accordingly, it proceeds to do the importing. You can choose to have a confirmation asking if only the mono tracks have to be panned to the center or all the new tracks have to. If its just the mono tracks it will only pan them. Else it will pan all the tracks to center. Hope it helps!

                                          PS: It only processes the selected tracks. I updated the code for deletion in one go and renaming in one go.

                                          // Determines whether the file is mono (both channels identical),
                                          // or if one channel is silent. Returns "mono", "left", "right", or "stereo".
                                          function areChannelsIdentical(audioFilePath) {
                                              var cmdFFMPEG = `
                                                  audioFilePath="${audioFilePath}";
                                                  mono=$(/opt/homebrew/bin/ffmpeg -hide_banner -i "$audioFilePath" \
                                                  -filter_complex "[0:a]channelsplit=channel_layout=stereo[left][right];[left][right]amix=inputs=2:weights='1 -1'[diff];[diff]astats=metadata=1" \
                                                   -f null - 2>&1 | grep "Max level" | head -n1 | awk '{print $NF}');
                                                  if [ $(echo "$mono <= 0.0001" | bc -l) -eq 1 ]; then 
                                                      echo "mono"; 
                                                  else
                                                      left=$(/opt/homebrew/bin/ffmpeg -hide_banner -i "$audioFilePath" \
                                                      -filter_complex "[0:a]channelsplit=channel_layout=stereo[left][right];[left][right]amix=inputs=2:weights='1 0'[diff];[diff]astats=metadata=1" \
                                                      -f null - 2>&1 | grep "Max level" | head -n1 | awk '{print $NF}');
                                                      right=$(/opt/homebrew/bin/ffmpeg -hide_banner -i "$audioFilePath" \
                                                      -filter_complex "[0:a]channelsplit=channel_layout=stereo[left][right];[left][right]amix=inputs=2:weights='0 1'[diff];[diff]astats=metadata=1" \
                                                      -f null - 2>&1 | grep "Max level" | head -n1 | awk '{print $NF}');
                                                      if [ $(echo "$left > 0.0001" | bc -l) -eq 1 ] && [ $(echo "$right <= 0.0001" | bc -l) -eq 1 ]; then 
                                                          echo "left";
                                                      elif [ $(echo "$left <= 0.0001" | bc -l) -eq 1 ] && [ $(echo "$right > 0.0001" | bc -l) -eq 1 ]; then 
                                                          echo "right";
                                                      else 
                                                          echo "stereo"; 
                                                      fi;
                                                  fi
                                              `;
                                              // (Optionally, you can remove extra whitespace.)
                                              var result = sf.system.exec({ commandLine: cmdFFMPEG }).result.trim();
                                              // log("areChannelsIdentical(" + audioFilePath + ") -> " + result);
                                              return result;
                                          }
                                          
                                          //Unified Split/Delete Function
                                          
                                          function splitAndDeleteTracksUnified(combinedTrackNames, trackChannelType) {
                                              // Select all original tracks.
                                              sf.app.proTools.selectTracksByName({ selectionMode: 'Replace', trackNames: combinedTrackNames });
                                              var namesBefore = sf.app.proTools.tracks.invalidate().allItems.map(t => t.name);
                                          
                                              // Execute the split command.
                                              sf.ui.proTools.menuClick({ menuPath: ['Track', 'Split into Mono'] });
                                              // alert('Click OK when all tracks are split');
                                          
                                              var namesAfter = sf.app.proTools.tracks.invalidate().allItems.map(t => t.name);
                                              // New names are those that did not exist before splitting.
                                              var newNames = namesAfter.filter(t => namesBefore.indexOf(t) === -1);
                                          
                                              // Build desired new names for each original track.
                                              var desiredNames = combinedTrackNames.map(function (orig) {
                                                  var type = trackChannelType[orig];
                                                  // For "mono" and "left" tracks, we want the left channel (.L); for "right" tracks, .R.
                                                  var suffix = (type === "right") ? ".R" : ".L";
                                                  return orig + suffix;
                                              });
                                          
                                              // Build the deletion list: delete all original tracks plus any new split that isn't desired.
                                              var deleteTracks = combinedTrackNames.slice(); // originals.
                                              deleteTracks = deleteTracks.concat(newNames.filter(function (n) {
                                                  return desiredNames.indexOf(n) === -1;
                                              }));
                                          
                                              if (deleteTracks.length > 0) {
                                                  sf.app.proTools.selectTracksByName({ selectionMode: 'Replace', trackNames: deleteTracks });
                                                  sf.ui.proTools.menuClick({ menuPath: ['Track', 'Delete...'] });
                                                  sf.ui.proTools.confirmationButtonDialog.elementWaitFor();
                                                  sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Delete").first.elementClick();
                                              }
                                              // The kept tracks are the new splits that match one of the desired names.
                                              var keptTracks = newNames.filter(function (n) {
                                                  return desiredNames.indexOf(n) !== -1;
                                              });
                                              return keptTracks;
                                          }
                                          
                                          /* 
                                          Unified Rename Function:
                                            Removes any trailing ".L" or ".R" from each track name.
                                            Returns an array with the new (renamed) track names.
                                          */
                                          function renameTracksWithoutSuffixUnified(trackNames) {
                                              var renamed = [];
                                              sf.app.proTools.selectTracksByName({ selectionMode: 'Replace', trackNames: trackNames });
                                              trackNames.forEach(function (name) {
                                                  var newName = name.replace(/(\.L|\.R)$/, "");
                                                  sf.app.proTools.renameTrack({ oldName: name, newName: newName });
                                                  renamed.push(newName);
                                              });
                                              return renamed;
                                          }
                                          
                                          // Pans the provided tracks to center.
                                          function panTracksToCenter(trackNames) {
                                              sf.ui.proTools.trackSelectByName({ names: trackNames });
                                              sf.ui.proTools.selectedTrack.trackScrollToView();
                                              sf.ui.proTools.selectedTrack.outputWindowButton.elementClick();
                                              sf.app.proTools.selectTracksByName({ selectionMode: 'Replace', trackNames: trackNames });
                                              sf.ui.proTools.mainTrackOutputWindow.sliders.whoseTitle.is("Front Pan Knob").first.mouseClickElement({ isOption: true, isShift: true });
                                              sf.ui.proTools.mainTrackOutputWindow.windowClose();
                                          }
                                          
                                          // Retrieves the audio file path of the first clip on the given track.
                                          function getAudioFilePathOfFirstClipOnTrack(trackName) {
                                              sf.app.proTools.selectTracksByName({ trackNames: [trackName], selectionMode: 'Replace' });
                                              sf.keyboard.press({ keys: "return, control+tab" });
                                              return sf.app.proTools.getFileLocation({ fileFilters: ['SelectedClipsTimeline'] }).fileLocations[0].path;
                                          }
                                          
                                          // Main processing function.
                                          function main() {
                                          
                                              const notifID = sf.system.newGuid().guid;
                                              sf.ui.proTools.appActivate();
                                              sf.app.proTools.invalidate();
                                          
                                              // Process only selected stereo audio tracks.
                                              var stereoTracks = sf.app.proTools.tracks.allItems.filter(t =>
                                                  t.format === 'TFStereo' && t.type === 'Audio' && t.isSelected
                                              );
                                              var stereoTrackNames = stereoTracks.map(t => t.name);
                                          
                                              // Build arrays for each channel type and record each track's detected type.
                                              var monoTracks = [], leftTracks = [], rightTracks = [];
                                              var trackChannelType = {};  // e.g. { "Track1": "mono", "Track2": "left", "Track3": "right" }
                                          
                                              stereoTrackNames.forEach(function (trackName) {
                                                  var audioPath = getAudioFilePathOfFirstClipOnTrack(trackName);
                                                  var type = areChannelsIdentical(audioPath);
                                                  trackChannelType[trackName] = type;
                                          
                                                  //Notification
                                                  sf.interaction.notify({
                                                      uid: notifID,
                                                      title: "Detection",
                                                      message: `Track ${trackName} detected as ${type}`,
                                                      keepAliveMs: 1500
                                                  });
                                          
                                                  if (type === "mono") {
                                                      monoTracks.push(trackName);
                                                  } else if (type === "left") {
                                                      leftTracks.push(trackName);
                                                  } else if (type === "right") {
                                                      rightTracks.push(trackName);
                                                  }
                                              });
                                          
                                              // Clear timeline selection.
                                              sf.keyboard.press({ keys: 'return' });
                                          
                                              // Combine all tracks that need processing.
                                              var combinedTracks = monoTracks.concat(leftTracks, rightTracks);
                                              // Split-and-delete on the combined tracks in one go.
                                              var processedTracks = splitAndDeleteTracksUnified(combinedTracks, trackChannelType);
                                              // Rename (remove the trailing ".L" or ".R") in one go.
                                              var renamedTracks = renameTracksWithoutSuffixUnified(processedTracks);
                                          
                                              // Ask if only mono tracks should be panned.
                                              var allKeptTracks;
                                              if (confirm("Pan all tracks to center? Cancel will Pan only mono tracks.")) {
                                                  allKeptTracks = renamedTracks;
                                              } else {
                                                  // Since the rename function removes the suffix, filter by original names that were mono.
                                                  allKeptTracks = renamedTracks.filter(function (name) {
                                                      return monoTracks.indexOf(name) !== -1;
                                                  });
                                              }
                                              // Ensure allKeptTracks is an array of strings.
                                              allKeptTracks = allKeptTracks.map(function (item) { return String(item); });
                                          
                                              panTracksToCenter(allKeptTracks);
                                          }
                                          
                                          main();
                                          
                                          1. Whoa. EPIC!

                                            1. SSreejesh Nair @Sreejesh_Nair
                                                2025-02-04 17:19:09.996Z

                                                I got the idea from your initial implementation! All credit to you for the idea!

                                            2. J
                                              In reply tochrscheuer:
                                              julien creu @julien_creu
                                                2025-02-04 16:29:15.139Z

                                                Whoh Wonderful @Sreejesh_Nair , thank you so much!!
                                                If someone needs it, here's a snippet that keeps the leftTracks and rightTracks panned to left and right!
                                                Thanks guys for this superb script!!

                                                function panTracksToLeft(trackNames) {
                                                    sf.app.proTools.selectTracksByName({
                                                        selectionMode: 'Replace',
                                                        trackNames: trackNames,
                                                    });
                                                    //Pan new mono tracks to the left
                                                    sf.ui.proTools.selectedTrack.outputWindowButton.elementClick({ executionMode: 'Background' });
                                                    sf.ui.proTools.mainTrackOutputWindow.elementWaitFor();
                                                    sf.ui.proTools.mainTrackOutputWindow.textFields[1].elementClick();
                                                    sf.keyboard.type({
                                                        text: "-100"
                                                    });
                                                    sf.keyboard.press({
                                                        keys: 'return'
                                                    });
                                                    sf.ui.proTools.mainTrackOutputWindow.windowClose();
                                                    sf.ui.proTools.mainTrackOutputWindow.elementWaitFor({
                                                        waitType: 'Disappear'
                                                    });
                                                }
                                                
                                                function panTracksToRight(trackNames) {
                                                    sf.app.proTools.selectTracksByName({
                                                        selectionMode: 'Replace',
                                                        trackNames: trackNames,
                                                    });
                                                    //Pan new mono tracks to the right
                                                    sf.ui.proTools.selectedTrack.outputWindowButton.elementClick({ executionMode: 'Background' });
                                                    sf.ui.proTools.mainTrackOutputWindow.elementWaitFor();
                                                    sf.ui.proTools.mainTrackOutputWindow.textFields[1].elementClick();
                                                    sf.keyboard.type({
                                                        text: "+100"
                                                    });
                                                    sf.keyboard.press({
                                                        keys: 'return'
                                                    });
                                                    sf.ui.proTools.mainTrackOutputWindow.windowClose();
                                                    sf.ui.proTools.mainTrackOutputWindow.elementWaitFor({
                                                        waitType: 'Disappear'
                                                    });
                                                }
                                                
                                                ////////replaces the panning function in the end of the original script//////
                                                    panTracksToCenter(monoTracks);
                                                    panTracksToLeft(leftTracks);
                                                    panTracksToRight(rightTracks);
                                                
                                                1. SSreejesh Nair @Sreejesh_Nair
                                                    2025-02-04 17:20:50.170Z2025-02-04 19:17:30.707Z

                                                    I think this will only run for the first track in trackNames. It has to be modified for each of the entries in trackNames. The below modified functions run well on my system. But if you split tracks into mono, it will automatically be panned to left for left channel and right for right channel.

                                                    function panTracksToLeft(trackNames) {
                                                        trackNames.forEach(trackName => {
                                                            // Select the current track by wrapping the track name in an array
                                                            sf.app.proTools.selectTracksByName({
                                                                selectionMode: 'Replace',
                                                                trackNames: [trackName],
                                                            });
                                                    
                                                            // Pan the current track to the left
                                                            sf.ui.proTools.selectedTrack.outputWindowButton.elementClick({ executionMode: 'Background' });
                                                            sf.ui.proTools.mainTrackOutputWindow.elementWaitFor();
                                                            sf.ui.proTools.mainTrackOutputWindow.textFields.whoseTitle.is("Front Pan Numerical").first.elementClick();
                                                            sf.keyboard.type({
                                                                text: "-100"
                                                            });
                                                            sf.keyboard.press({
                                                                keys: 'return'
                                                            });
                                                    
                                                        });
                                                    }
                                                    
                                                    
                                                    function panTracksToRight(trackNames) {
                                                        trackNames.forEach(trackName => {
                                                            sf.app.proTools.selectTracksByName({
                                                                selectionMode: 'Replace',
                                                                trackNames: [trackName],
                                                            });
                                                    
                                                            //Pan the current track to the right
                                                            sf.ui.proTools.selectedTrack.outputWindowButton.elementClick({ executionMode: 'Background' });
                                                            sf.ui.proTools.mainTrackOutputWindow.elementWaitFor();
                                                            sf.ui.proTools.mainTrackOutputWindow.textFields.whoseTitle.is("Front Pan Numerical").first.elementClick();
                                                    
                                                            sf.keyboard.type({
                                                                text: "+100"
                                                            });
                                                            sf.keyboard.press({
                                                                keys: 'return'
                                                            });
                                                    
                                                        });
                                                    }