Protools "deselect tracks" not working
Title
Protools "deselect tracks" not working
Content type
script
Content name
Test Script Tim Artist Archive
Content package
Tim Test
Version info
OS: macOS 15.6.1
SoundFlow: 6.0.6
Pro Tools: 24.10.3.210
What do you expect to happen when you run the script?
Rename audio files based on prefilled information
Are you seeing an error?
error on line 232 can't deselect tracks
What happens when you run the script?
How were you running this script?
I clicked the Run Script or Run Macro button in SoundFlow
How important is this issue to you?
5
Details
{
"textExpected": "Rename audio files based on prefilled information",
"textError": "error on line 232\ncan't deselect tracks",
"inputHowRun": "I clicked the Run Script or Run Macro button in SoundFlow",
"inputImportance": 5,
"textTitle": "Protools \"deselect tracks\" not working"
}
Source
const projectDir = sf.ui.proTools.mainWindow.sessionPath.split('/').slice(0, -1).join('/');
const tempoMapFileName = 'tempomap.txt'
function sanitisedArtistTrackName(artist, songTitle, bpm, instrumentName, sampleRate, bitDepth) {
let rawNameString = `${artist} - ${songTitle} - ${String(bpm).trim()} - ${instrumentName} - ${String(sampleRate).trim()}${bitDepth}`;
rawNameString = rawNameString.replace(/[\?\*\.\'\"']/g, "_");
rawNameString = rawNameString.replace(/[\\]/g, "");
return rawNameString.toLowerCase();
}
function extractNumber(str) {
// Extract the first number found in the string
const match = str.match(/-?\d+/);
if (!match) {
throw new Error("No number found in the string");
}
const num = match[0];
return num;
}
function singlePressKey(key) {
sf.keyboard.press({
keys: key,
repetitions: 1
});
}
function formatExtractedNumber(str) {
// Extract the first number found in the string
const match = str.match(/\d+/);
if (!match) {
throw new Error("No number found in the string");
}
const num = match[0];
// Convert the extracted number to a string and pad it
return num.toString().padStart(3, '0');
}
function getTempoWriteMap() {
let selection = sf.ui.proTools.selectionGet()
let {inTime, outTime} = sf.app.proTools.getTimelineSelection();
let originalOut = outTime;
let offsetBars = extractNumber(selection.mainCounter) - 1;
sf.ui.proTools.nudgeSet({executionMode: "Foreground", value: "0|0|480"});
//workaround for bug - nudgeSet does set value but not mode
const nudgeBtn = sf.ui.proTools.mainWindow.gridNudgeCluster.nudgeControls
nudgeBtn.nudgeValuePopupButton.popupMenuSelect({ menuPath: ["Bars|Beats"] });
singlePressKey('return');
sf.ui.proTools.mainWindow.invalidate();
sf.ui.proTools.selectionSet(
{
selectionStart: selection.selectionStart,
selectionEnd: selection.selectionStart
}
);
singlePressKey('period');
singlePressKey('comma');
sf.ui.proTools.mainWindow.invalidate();
let tempoField = prepareTempoField()
let i = 0;
let currentTimePos = inTime;
let curSelection;
let oldTempo = _getTempo(tempoField);
let newTempo;
let tempoMapRaw = new Array();
tempoMapRaw.push(`${selection.mainCounter}: ${oldTempo}`);
while(Number(currentTimePos) < Number(originalOut)) {
singlePressKey('period');
newTempo = _getTempo(tempoField);
if(Number(newTempo) != Number(oldTempo)){
curSelection = sf.ui.proTools.selectionGet();
oldTempo = newTempo;
tempoMapRaw.push(`${curSelection.mainCounter}: ${newTempo}`);
}
let {inTime, outTime} = sf.app.proTools.getTimelineSelection();
currentTimePos = inTime;
sf.ui.proTools.mainWindow.invalidate();
}
sf.ui.proTools.selectionSet(
{
selectionStart: selection.selectionStart,
selectionEnd: selection.selectionEnd
}
);
if(tempoMapRaw.length > 1) {
let tempoMapSanatised = new Array();
var count = 0;
tempoMapRaw.forEach( bar => {
if(count == 0)
{
bar = bar.replace(/ -?\d+\| \d+\| \d+/, '1| 0| 000')
}
else
{
let originalBar = extractNumber(bar)
let match = bar.replace(/-?\d+/, '');
bar = String(originalBar - offsetBars) + match.trim()
}
tempoMapSanatised.push(bar);
count++;
})
var text = tempoMapSanatised.join('\n');
var fullTempoMapPath = [projectDir, tempoMapFileName].join('/');
if(sf.file.exists({path: fullTempoMapPath})) {
sf.file.delete({path: fullTempoMapPath})
}
sf.file.writeText({path: fullTempoMapPath, text: text})
log(`Various BPM - created Tempo Map at ${fullTempoMapPath}`);
return 'variousBPM see tempomap file';
}
else {
return String(oldTempo).replace('.', '_') + 'bpm'
}
}
function prepareTempoField() {
//Check if MIDI Controls Cluster is visible and if not open it.
sf.ui.proTools.appActivateMainWindow();
sf.ui.proTools.mainWindow.invalidate();
const midiControlCluster = sf.ui.proTools.mainWindow.midiControlCluster;
const tempoField = midiControlCluster.getFirstWithTitle('Tempo value');
if (!tempoField.exists) {
//Open the Toolbar Options Menu
sf.ui.proTools.mainWindow.toolbarOptionsMenuButton.elementClick();
const zoomControls = sf.ui.proTools.windows.whoseTitle.endsWith("Zoom Controls").first;
//Wait for the Toolbar Options Menu to be open (by waiting for first menu Item)
zoomControls.elementWaitFor();
//Enable MIDI Controls
zoomControls.menuItems.whoseTitle.is("MIDI Controls").first.elementClick();
//Close the Toolbar Options Menu
sf.ui.proTools.appActivateMainWindow();
//The main window changed because the midi cluster was added,
//so invalidate the main window
sf.ui.proTools.mainWindow.invalidate();
}
// Enable Bars|Beats view
sf.ui.proTools.menuClick({
menuPath: [
"View", "Main Counter", "Bars|Beats",
]
}, `Could not find the menu item in Pro Tools`);
return tempoField;
}
function _getTempo(tempoField) {
//Collect BPM into variable beatsPerMinute
return tempoField.value.invalidate().value;
}
function getTempo() {
let tempoField = prepareTempoField();
return _getTempo(tempoField);
}
function getPTVersion() {
const versNumberArray = sf.ui.proTools.appVersion.split(".")
let [year, month, increment] = versNumberArray
month = "0" + month.slice(-2)
increment = "0" + increment.slice(-2)
let versionNumber = Number(year + month + increment)
return versionNumber
}
const PACKAGE_DIRECTORY = sf.soundflow.thisPackage.getDirectory().path;
const previousValuesPath = `${PACKAGE_DIRECTORY}/Previous_Entries_Artist_Archive.json`
// ////////////////
// ///// MAIN /////
// ////////////////
sf.ui.proTools.appActivateMainWindow()
// Check for and load previous entries
let prevArtist, prevSongTitle;
if (sf.file.exists({ path: previousValuesPath }).exists) {
let previousEntries = sf.file.readJson({ path: previousValuesPath }).json;
({
prevArtist,
prevSongTitle
} = previousEntries)
}
// Instruct user to select tracks to be renamed
sf.interaction.displayDialog({
prompt: "Select tracks in session to be renamed and click OK",
buttons: ["Cancel", "OK"],
defaultButton: "OK"
}).text
// Show only selected tracks
const trackListPopupBtn = sf.ui.proTools.mainWindow.trackListPopupButton
trackListPopupBtn.popupMenuSelect({ menuPath: ["Show Only Selected Tracks"] });
// get artist name from user
const artist = sf.interaction.displayDialog({
prompt: "Enter artist name:",
defaultAnswer: prevArtist ? prevArtist : "<Band Name>",
buttons: ["Cancel", "OK"],
defaultButton: "OK"
}).text
// get Song name from user
const songTitle = sf.interaction.displayDialog({
prompt: "Enter song title:",
defaultAnswer: prevSongTitle ? prevSongTitle : "<Song Title>",
buttons: ["Cancel", "OK"],
defaultButton: "OK"
}).text
const { button } = sf.interaction.displayDialog({
buttons: ['Single', 'Various', 'Cancel'],
defaultButton: 'Single',
cancelButton: 'Cancel',
prompt: 'Does this song contain various BPM?',
});
const variousBPM = button === 'Various'
// Get sample rate and bit depth from Session Setup window
// Open Session Setup if neccessary
const sessionSetupWin = sf.ui.proTools.windows.whoseTitle.is("Session Setup").first
if (!sessionSetupWin.exists) {
sf.ui.proTools.menuClick({ menuPath: ["Setup", "Session"] })
sessionSetupWin.elementWaitFor()
}
// Get sample rate
const sampleRate = sessionSetupWin.groups.whoseTitle.is("Session Format").first
.children.whoseRole.is("AXStaticText").allItems[2].value.invalidate().value
.split(" k")[0] + "k"
// Get bit depth
const bitDepth = sessionSetupWin.groups.whoseTitle.is("Session Format").first.
popupButtons.allItems[1].value.invalidate().value.split(" B")[0] + "b";
//Close Session Info window
sessionSetupWin.windowClose()
sessionSetupWin.elementWaitFor({ waitType: "Disappear" })
//Save entries to disk to reuse on next run
const previousEntries = {
prevArtist: artist,
prevSongTitle: songTitle
}
sf.file.writeJson({ json: previousEntries, path: previousValuesPath })
// Ensure time line and edit selection are linked
var linkeTimeLineAndEditBtn = sf.ui.proTools.mainWindow.cursorToolCluster.buttons
.whoseTitle.is("Link Track and Edit Selection").first
if (linkeTimeLineAndEditBtn.value.invalidate().value !== "Selected") linkeTimeLineAndEditBtn.elementClick();
var tempo;
if(variousBPM) {
tempo = getTempoWriteMap();
}
else {
tempo = getTempo();
}
// Have user confirm entries
sf.interaction.displayDialog({
title: "Confirm Rename Tracks",
prompt: `Confirm entries before proceeding.
(Cancel to start over)
Artist: ${artist}
Song Title: ${songTitle}
Tempo: ${tempo}
Sample Rate: ${sampleRate}
Bit Depth: ${bitDepth}
-----------
Sample Track Name = ${sanitisedArtistTrackName(artist, songTitle, tempo, "Kick_In", sampleRate, bitDepth)}`,
buttons: ["Cancel", "OK"],
defaultButton: "OK"
});
linkeTimeLineAndEditBtn = sf.ui.proTools.mainWindow.cursorToolCluster.buttons
.whoseTitle.is("Link Track and Edit Selection").first
if (linkeTimeLineAndEditBtn.value.invalidate().value !== "Selected") linkeTimeLineAndEditBtn.elementClick();
// Get visible track headers
let selectedTrackHeaders = sf.ui.proTools.visibleTrackHeaders;
//Scroll to view first track
sf.ui.proTools.trackDeselectAll()
sf.ui.proTools.trackSelectByName({ names: [selectedTrackHeaders[0].normalizedTrackName] })
sf.ui.proTools.selectedTrack.trackScrollToView()
// Open rename window
selectedTrackHeaders[0].popupButtons.first.mouseClickElement({ clickCount: 2 })
// Wait for rename window
sf.ui.proTools.windows.whoseTitle.is(selectedTrackHeaders[0].popupButtons.first.value.invalidate().value).first.elementWaitFor();
//Get the elements of text fields and buttons from rename window
const [trackName, comments] = sf.ui.proTools.focusedWindow.invalidate().textFields.map(x => x)
const [ok, cncl, pr, next] = sf.ui.proTools.focusedWindow.buttons.map(x => x)
let previousName = '';
let fulltrackName = '';
let oldTrackName = '';
//Rename Tracks
selectedTrackHeaders.forEach(track => {
// Construct track name
while (previousName == trackName.value.invalidate().value)
{
sf.wait({ intervalMs: 2 })
}
oldTrackName = trackName.value.invalidate().value
fulltrackName = `${sanitisedArtistTrackName(artist, songTitle, tempo, oldTrackName, sampleRate, bitDepth)}`;
do { // Set new track name
trackName.elementSetTextFieldWithAreaValue({
value: fulltrackName,
});
}
while(trackName.value.invalidate().value != fulltrackName)
// If it's the last track press ok, else press next
if (selectedTrackHeaders.indexOf(track) == selectedTrackHeaders.length - 1) {
ok.elementClick()
} else {
next.elementClick()
};
})
// ask user to confirm new track names before proceeding
const confirmTrackNames = sf.interaction.displayDialog({
title: "Confirm Rename Audio Files",
prompt: `Please review and confirm track names before proceeding to renaming audio files.
(You may need to make the track list wider to see full track names.)
You can revert to the last saved version of the session if you would like to start again.
You may also manually rename any mislabeled tracks before clicking "Proceed"`,
buttons: ["Cancel", "Revert", "Proceed"]
}).button;
// Revert to saved if requested
if (confirmTrackNames == "Revert") {
sf.ui.proTools.menuClick({ menuPath: ["File", "Revert"], looseMatch: true })
throw 0
}
// Rename Audio Files
sf.ui.proTools.mainWindow.invalidate();
// refresh track headers
selectedTrackHeaders = sf.ui.proTools.visibleTrackHeaders
if (getPTVersion() < 230600) {
// If running older version of proTools run the following:
const refreshMenus = () => sf.keyboard.press({ keys: "n", repetitions: 2, fast: true })
selectedTrackHeaders.forEach(track => {
sf.ui.proTools.trackSelectByName({ names: [track.normalizedTrackName] })
refreshMenus()
sf.ui.proTools.menuClick({ menuPath: ["Edit", "Select All"] })
refreshMenus()
sf.ui.proTools.menuClick({ menuPath: ["Clip", "Rename..."] })
const clipNameWindow = sf.ui.proTools.windows.whoseTitle.is("Name").first
clipNameWindow.elementWaitFor()
//Define clip rename window elements
const nameGroup = clipNameWindow.groups.whoseTitle.is("Name").first
const clipNameField = nameGroup.textFields.first;
const clipAndDiskBtn = nameGroup.radioButtons.whoseTitle.is("name clip and disk file").first;
const nameOkBtn = clipNameWindow.buttons.whoseTitle.is("OK").first
while (true) {
if (clipNameField.value.invalidate().value !== track.normalizedTrackName) {
clipNameField.elementClick()
sf.keyboard.press({
keys: "backspace",
repetitions: 2
});
sf.wait({ intervalMs: 25 })
clipNameField.elementSetTextFieldWithAreaValue({ value: track.normalizedTrackName, useMouseKeyboard: true })
sf.wait({ intervalMs: 300 })
clipAndDiskBtn.elementClick()
} else { break; }
}
nameOkBtn.elementClick()
})
} else {
// If new version of PT run this
selectedTrackHeaders.forEach(track => {
//sf.app.proTools.selectAllClipsOnTrack({ trackName: track.normalizedTrackName });
sf.ui.proTools.trackSelectByName({ names: [track.normalizedTrackName] })
sf.ui.proTools.mainWindow.invalidate();
let selectedClip = sf.app.proTools.getSelectedClipInfo().clips
// log(selectedClip)
let { inTime, outTime } = sf.app.proTools.getTimelineSelection()
// log(`In Time: ${inTime}; Out Time: ${outTime}`)
// selectedClip.forEach(clip => {
// log(`Clip: In Time: ${clip.startTime}; Out Time: ${clip.endTime}`)
// })
let hasSelectedClip = selectedClip.filter(clip => clip.startTime >= inTime && clip.endTime <= outTime).length > 0
if(hasSelectedClip) {
sf.app.proTools.renameSelectedClip({ newName: track.normalizedTrackName, renameFile: true })
}
})
sf.app.proTools.refreshAllModifiedAudioFiles()
}
// Show previously selected tracks
trackListPopupBtn.popupMenuSelect({ menuPath: ["Restore Previously Shown Tracks"] });
sf.interaction.notify({ title: "Rename track and clips complete!" })
Links
User UID: bfBv1LrWwjVtTpPyywxpGsl1VGY2
Feedback Key: sffeedback:bfBv1LrWwjVtTpPyywxpGsl1VGY2:-OlGjwaoaQTXVN8ju5tQ
Feedback ZIP: 0ZOfxVLBqXxtQG0yxwckvUVPVWYhXYNeTzpuQYbuMI1FokwC8xfmwviwGbIDxNE88bjSBSROwdKHl/wwzMcS/I6nXis6sU+Mu4d0COdYOosxdLj57CxK2c9xEM4PYtG8yjcwduxDbEnIJ4xLK/vuUlMSWuf5JHWoihrkh3m9Tfj1h1fjmg6tmuGoHI0eBgI0Adr6A+Y/o41LhNg6n4SAdmvbKbMm0FQdKIx6wvFy/U9pWxSMdB8EjGho8vQaTdKx6FNgShR48MjD+eP+1XtJWmhlNQc8b9dx/40vr9hk61/5t50bCiTQ+mSSuvOrcS4M2bwhKAk/sLbRhF2wRZh99WeuFuukKtNhWe0K6nvGteQ=
In reply toRob_Sannen⬆:Chris Shaw @Chris_Shaw2026-02-12 21:23:31.974ZI believe you're referring to line 322.
You may not need to deselect tracks since you're selecting tracks immediately afterward.
Try commenting / removingsf.ui.proTools.trackDeselectAll.
If this doesn't work then also change the next line to this:sf.ui.proTools.trackSelectByName({ names: [selectedTrackHeaders[0].normalizedTrackName], deselectOthers: true })- RIn reply toRob_Sannen⬆:Rob Sannen @Rob_Sannen
Thanks for your reply!
Line 322 is the correct one. The
sf.ui.proTools.trackDeselectAllwas only added as a debug measure, originally (and when commenting out the deselect) the exact same error occured in the next line:
sf.ui.proTools.trackSelectByName({ names: [selectedTrackHeaders[0].normalizedTrackName] })Unfortunately your suggestion didn´t solve the issue.
Chad Wahlbrink @Chad2026-02-17 21:01:52.007ZHi, @Rob_Sannen,
As a first troubleshooting step, could you please manually update to the latest version of SoundFlow here and see if that resolves your issue?
https://soundflow.org/account/install
If not, could you provide a quick screen recording of the behavior and share the link via Google Drive or Dropbox? That would be really helpful for narrowing down the issue you are experiencing.
- RRob Sannen @Rob_Sannen
hi Chad,
Please find a link of the screen recording below.
https://drive.google.com/file/d/1rxBcXOXzpZLNzc5Excu7_Y3-Gveqnci8/view?usp=drive_link
Bests,
Rob
Chad Wahlbrink @Chad2026-02-27 21:45:41.215ZThanks, @Rob_Sannen,
I'm currently unable to reproduce this issue, but I do see it happening in your video. I attempted to run your script on Pro Tools 2025.10.4.2 (what I had on hand) and Pro Tools 2025.12.1 and it worked in both scenarios.
A few more questions for you.
The error in the logs are:
12.02.2026 13:05:57.38 <info> [Backend]: ProTools version: 24.10.3.210 class: PT2024_10 ProTools processID: 75556 12.02.2026 13:05:57.38 <info> [Backend]: Pro Tools language: English 12.02.2026 13:05:57.38 <info> [Backend]: Logging error in action (01) PtAppSelectTracksByNameAction: This command requires Pro Tools to run in Rosetta / x64 mode 12.02.2026 13:05:57.38 <info> [Backend]: Logging error in action (01) DeselectTracksAction: Could not deselect tracks 12.02.2026 13:05:57.38 <info> [Backend]: !! Command Error: Test Script Tim Artist Archive [user:cm94t6d3j0000ah10q9rkihd6:cm9bxmhyw0001uc102clk0nik]: Could not deselect tracks (Test Script Tim Artist Archive: Line 322) This command requires Pro Tools to run in Rosetta / x64 mode 12.02.2026 13:05:57.38 <info> [Backend]: << Command: Test Script Tim Artist Archive [user:cm94t6d3j0000ah10q9rkihd6:cm9bxmhyw0001uc102clk0nik]Can you provide more details on what type of computer you are running this on? What kind of mac? I've never seen the
This command requires Pro Tools to run in Rosetta / x64 modeerror before, but this suggests there may be some kind of system issue we are missing.Other options to try:
As Chris suggested, try changing lines 318 to 324 to this, which also works here for me:
// Get visible track headers let selectedTrackHeaders = sf.ui.proTools.visibleTrackHeaders; //Scroll to view first track // sf.ui.proTools.trackDeselectAll() sf.ui.proTools.trackSelectByName({ names: [selectedTrackHeaders[0].normalizedTrackName], deselectOthers: true }) sf.ui.proTools.selectedTrack.trackScrollToView();OR
Simplify to this, since
sf.ui.proTools.selectedTrack.trackScrollToView();will automatically deselect the other tracks and only apply to the first selected track - changedselectedTrackHeaderstosf.ui.proTools.selectedTrackHeaders;as well:// Get selected track headers let selectedTrackHeaders = sf.ui.proTools.selectedTrackHeaders; //Scroll to view first track sf.ui.proTools.selectedTrack.trackScrollToView();Let me know if any of those options get you in the right direction.
Chad Wahlbrink @Chad2026-02-27 21:47:39.107ZFinally, I would tweak the end of your script to read like this:
else { // If new version of PT run this selectedTrackHeaders.forEach(track => { //sf.app.proTools.selectAllClipsOnTrack({ trackName: track.normalizedTrackName }); sf.ui.proTools.trackSelectByName({ names: [track.normalizedTrackName] }) sf.ui.proTools.mainWindow.invalidate(); let selectedClip = sf.app.proTools.getSelectedClipInfo().clips // log(selectedClip) let { inTime, outTime } = sf.app.proTools.getTimelineSelection() // log(`In Time: ${inTime}; Out Time: ${outTime}`) // selectedClip.forEach(clip => { // log(`Clip: In Time: ${clip.startTime}; Out Time: ${clip.endTime}`) // }) let hasSelectedClip = selectedClip.filter(clip => clip.startTime >= Number(inTime) && clip.endTime <= Number(outTime)).length > 0 if (hasSelectedClip) { sf.app.proTools.renameSelectedClip({ newName: track.normalizedTrackName, renameFile: true }) } }) sf.app.proTools.refreshAllModifiedAudioFiles() } // Show previously selected tracks trackListPopupBtn.popupMenuSelect({ menuPath: ["Restore Previously Shown Tracks"] }); sf.interaction.notify({ title: "Rename track and clips complete!" })This line is what changed:
let hasSelectedClip = selectedClip.filter(clip => clip.startTime >= Number(inTime) && clip.endTime <= Number(outTime)).length > 0
- Progress