Replace clip with set length of room tone script misbehaving
Title
Replace clip with set length of room tone script misbehaving
What do you expect to happen when you run the script/macro?
This script is supposed to replace the current selection with a set length of room tone copied from another track, which is selected by name
Are you seeing an error?
What happens when you run this script?
The script works pretty much as intended, and frankly even when forced I can't make it actually fail, but it always seems to leave behind a very short additional clip at the end of the selection, instead of just replacing it outright. The additional clip seems to vary in length too
How were you running this script?
I used a Stream Deck button
How important is this issue to you?
4
Details
{ "inputExpected": "This script is supposed to replace the current selection with a set length of room tone copied from another track, which is selected by name", "inputIsError": false, "inputWhatHappens": "The script works pretty much as intended, and frankly even when forced I can't make it actually fail, but it always seems to leave behind a very short additional clip at the end of the selection, instead of just replacing it outright. The additional clip seems to vary in length too", "inputHowRun": { "key": "-MpfwmPg-2Sb-HxHQAff", "title": "I used a Stream Deck button" }, "inputImportance": 4, "inputTitle": "Replace clip with set length of room tone script misbehaving" }
Source
const originalTrack = sf.ui.proTools.selectedTrack;
const origSelection = sf.ui.proTools.selectionGet();
const { selectionStart, selectionEnd } = origSelection
function scrollToFirstTrack({ namePrefix }) {
let firstTrack = sf.ui.proTools.visibleTrackHeaders.filter(t => t.normalizedTrackName.startsWith(namePrefix))[0];
if (!firstTrack) throw `Could not find a visible track starting with "${namePrefix}"`;
firstTrack.trackScrollToView();
}
scrollToFirstTrack({
namePrefix: 'RT',
});
sf.ui.proTools.selectionSet({
selectionLength: "0:03.000",
});
sf.ui.proTools.editModeSet({
mode: "Shuffle",
});
sf.ui.proTools.menuClick({
menuPath: ["Edit","Copy"],
});
sf.ui.proTools.trackSelectByName({names:[originalTrack.normalizedTrackName]})
originalTrack.trackScrollToView()
sf.ui.proTools.selectionSet({
selectionStart,
selectionEnd
});
sf.ui.proTools.menuClick({
menuPath: ["Edit","Paste"],
});
Links
User UID: aha7HMyXWqeLdd6J0qx6NzNaTiD3
Feedback Key: sffeedback:aha7HMyXWqeLdd6J0qx6NzNaTiD3:-OQDEwYmBPBlIq_O3scA
Feedback ZIP: QacgcScl/qc19TOnim21ghwhJRrR3jwwV26KuFyE7sFHljFxsekNz4izPFapmrl77oaBU2Velh+saUhR56hxFuvrIvvuo96mrcZMQCaCjiSwyXjRvbOwegcXXO9Ul0pqb0waiT/XttlfGp5pjNmUdOhmDMnkwNxOS0smXpSrDe/TqfsZLOjDYykjUMaMg2RxB3Mzt7qpu5O/CdHBgvVUd064rqoMuMbh+0DZfxSWfaaSuWgiaH9cV4LoYbpSIeZYPSJ2E7q7H/8qnbRpo+amcVPGAPVrDt3MdLwzrtOjhVhVcB+Q3HwTwPQqJvNprd5QQiXUzK+qHW2oo5ZnrccYsVGcSLKSJ2dwODF5DO+OtNc=
- JJack Byrne @Jack_Byrne
Screenshot here, from another variant of the script that does .5 seconds. That small clip after the selection shouldn't be there, it's almost like a remnant of the clip I was wanting replaced.
There was an earlier version of this script that used restore last selection that didn't do this, but it was prone to failure by putting the room tone in the wrong place if I moved too fast. I posted the last version ages ago in an old thread (Wait for Previous Selection to change?) but I've had no luck tracking down a fix for the new version so far
- In reply toJack_Byrne⬆:Kitch Membery @Kitch2025-05-22 01:12:03.483Z
Hi @Jack_Byrne
It looks like you are using "Min:Secs" format for setting the selection set method, which is not sample accurate when making a selection.
In point form, can you describe the manual steps you take to achieve this workflow? With this information, I should be able to create a sample-accurate version of what you are after, with no pesky audio left behind. :-)
- In reply toJack_Byrne⬆:Kitch Membery @Kitch2025-05-22 01:47:18.837Z
Hi @Jack_Byrne
Actually, this script might do what you need. You just need to adjust the
lengthInMinsSecs
:-)const lengthInMinsSecs = "0:03.000" sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); function timeToSamples({ timeStr, sampleRate }) { // Split the string into minutes, seconds and milliseconds const [minPart, secMilliPart] = timeStr.split(':'); const [secPart, milliPart] = secMilliPart.split('.'); // Convert each part to numbers const minutes = parseInt(minPart, 10); const seconds = parseInt(secPart, 10); const milliseconds = parseInt(milliPart, 10); // Total time in seconds const totalSeconds = (minutes * 60) + seconds + (milliseconds / 1000); // Convert to samples const samples = Math.round(totalSeconds * sampleRate); return samples; } function main() { const selectedTrack = sf.ui.proTools.selectedTrack; const selectedTrackName = selectedTrack.normalizedTrackName; const trackSelection = sf.app.proTools.getTimelineSelection(); const { inTime, outTime } = trackSelection; const roomToneTrack = sf.app.proTools.tracks.invalidate().allItems.find(t => t.name.startsWith("RT")).name; sf.app.proTools.selectTracksByName({ trackNames: [roomToneTrack], }); const sampleRate = sf.app.proTools.getSessionSampleRate().sampleRateNumerical; const lengthInSamples = timeToSamples({ timeStr: lengthInMinsSecs, sampleRate }); sf.app.proTools.setTimelineSelection({ inTime, outTime: String(Number(inTime) + lengthInSamples) }); sf.app.proTools.copy(); sf.app.proTools.selectTracksByName({ trackNames: [selectedTrackName], }); sf.app.proTools.setTimelineSelection({ inTime, outTime }); sf.app.proTools.setEditMode({ editMode: "Shuffle" }); sf.app.proTools.paste(); } main();
Let me know if that works for you. :-)
This would be a great candidate for turning into a Command Template with presets. To learn more about how to create a Command Template, see the following link.
https://soundflow.org/docs/how-to/custom-commands/command-templates
- JJack Byrne @Jack_Byrne
Hey Kitch, thanks!
Seems work so far, I'll stress test it a bit today, got a book due by EOD. Interestingly so far it seems to be faster than the old script
- In reply toKitch⬆:JJack Byrne @Jack_Byrne
Works as a template too!
Is there a way to easily share this with teammates now that I've built it, short of just sending them to this thread?
Kitch Membery @Kitch2025-05-22 15:57:49.804Z
Hi @Jack_Byrne
To share the command template with the SoundFlow community, you'll need to create a new package, place the command template in the package, and then publish the package to the SoundFlow store.
To learn how to publish a package, please see the following link.
Please note, if you need to share a package privately with your team, you’ll need a Business subscription for that. The business subscription gives you and members of your organization access to a private section of the SoundFlow Store, where you can securely share macros, scripts, decks, etc.
For further information about this, please reach out to support@soundflow.org.
- In reply toKitch⬆:JJack Byrne @Jack_Byrne
Hey @Kitch, had one issue crop up, though I can't confirm if it's related. I've got markers follow edit on, and I think they're drifting a bit due to using this new version of the script (some of this might be on Avid's end, they rarely stay perfect through a whole project). Part of the workflow sometimes is using a product called Pozotron that will analyse and flag issues for us, which spits out PT markers for the book. I then go through before starting and mark them properly, before going through and proofing the rest by hand, and I've noticed that they're starting to shift later as I work towards them, so something's clearly not getting tracked perfectly. It's a fairly easy solve (each time I hit one I just move it back and they all shuffle into place) but if there's any tweak in the script that would avoid this that would be handy
- JJack Byrne @Jack_Byrne
Replying to myself again here, I've figured out the cause but not the solution. If a clip that I'm replacing is already the length that I want to replace it with (say if I do it twice by accident) then the markers get pushed back by whatever the length is, and the clips don't move. Definitely a user error thing but I'd be interested to know if there's a way to idiot proof the script to deal with this. Maybe a delete before the paste?
Kitch Membery @Kitch2025-05-22 15:40:24.024Z
Hi @Jack_Byrne
Untested, but here is a possible fix. I've added a
sf.app.proTools.clear();
call before the paste.const lengthInMinsSecs = "0:03.000" sf.ui.proTools.appActivateMainWindow(); sf.ui.proTools.mainWindow.invalidate(); function timeToSamples({ timeStr, sampleRate }) { // Split the string into minutes, seconds and milliseconds const [minPart, secMilliPart] = timeStr.split(':'); const [secPart, milliPart] = secMilliPart.split('.'); // Convert each part to numbers const minutes = parseInt(minPart, 10); const seconds = parseInt(secPart, 10); const milliseconds = parseInt(milliPart, 10); // Total time in seconds const totalSeconds = (minutes * 60) + seconds + (milliseconds / 1000); // Convert to samples const samples = Math.round(totalSeconds * sampleRate); return samples; } function main() { const selectedTrack = sf.ui.proTools.selectedTrack; const selectedTrackName = selectedTrack.normalizedTrackName; const trackSelection = sf.app.proTools.getTimelineSelection(); const { inTime, outTime } = trackSelection; const roomToneTrack = sf.app.proTools.tracks.invalidate().allItems.find(t => t.name.startsWith("RT")).name; sf.app.proTools.selectTracksByName({ trackNames: [roomToneTrack], }); const sampleRate = sf.app.proTools.getSessionSampleRate().sampleRateNumerical; const lengthInSamples = timeToSamples({ timeStr: lengthInMinsSecs, sampleRate }); sf.app.proTools.setTimelineSelection({ inTime, outTime: String(Number(inTime) + lengthInSamples) }); sf.app.proTools.copy(); sf.app.proTools.selectTracksByName({ trackNames: [selectedTrackName], }); sf.app.proTools.setTimelineSelection({ inTime, outTime }); sf.app.proTools.setEditMode({ editMode: "Shuffle" }); sf.app.proTools.clear(); sf.app.proTools.paste(); } main();
- JJack Byrne @Jack_Byrne
Works perfectly!
- JIn reply toJack_Byrne⬆:Jack Byrne @Jack_Byrne
Little variant to this, is this possible @Kitch?
Occasionally I'll get a narrator will leave gaps that are slightly too long throughout a book, so I need to stop and shorten the room tone that's spliced throughout the book. Is it possible to check if a clip is longer than x length, and if so replace it with room tone of y? And can that be combined with repeat for all clips while I've got all the room tone selected in object select mode? I was messing around seeing if there was a way to do this with clip boundary nudge and using fades as a buffer to stop them getting too small (essentially Andrew Wade's 'dream gate' if you know of it) but as soon as shuffle mode is involved that stops working.
Not asking you to write the script, but if you think it's probably doable I'd like to take a pass at figuring it out myself first, then have some much smarter members show me all the errors I make 😆
- JJack Byrne @Jack_Byrne
Alright, I managed to find a forum post that got me halfway there, and I've janked together something that I think will work. Object select mode I reckon is out the window, but I'm pretty certain this will work with a bit of prep, I'll just have to move the finished room tone clips back up into the dialogue once it's finished then close up gaps. This is where I've ended up, most of this was pulled from something @raphaelsepulveda wrote here Get PT Selection Length and use for If Statement
The only thing I would want to add is to get a selection of room tone at a length for replacement so that I don't have to think about it, but so far on a quick test the theory is sound. It'll be the sort of thing I'd need to leave running overnight I suspect, but it gives me an option for prep if I've got a tricky job that needs manual tweaks every couple of sentences
function getSelectionInMS() { const SECONDS_IN_A_MINUTE = 60; const MS_IN_A_SECOND = 1000; const MS_IN_A_MINUTE = MS_IN_A_SECOND * SECONDS_IN_A_MINUTE; let selectionLengthMinSecs; sf.ui.proTools.mainCounterDoWithValue({ targetValue: "Min:Secs", action: () => { selectionLengthMinSecs = sf.ui.proTools.selectionGet().selectionLength.trim(); } }); // @ts-ignore const minutes = Number(selectionLengthMinSecs.split(":")[0]); // @ts-ignore const [seconds, ms] = selectionLengthMinSecs.slice(2).split(".").map(value => Number(value)); return (minutes * MS_IN_A_MINUTE) + (seconds * MS_IN_A_SECOND) + ms; } sf.ui.proTools.appActivateMainWindow(); function clipLengthCheck() { const selectionInMS = getSelectionInMS(); if (selectionInMS > 1500) { sf.ui.proTools.menuClick({ menuPath: ["Edit", "Paste"] }); } else { } } sf.ui.proTools.clipDoForEachSelectedClip({ action: clipLengthCheck });