Identical Marker Navigation
Has anyone sorted out a way to tab back and forth through identically named markers? (So instead of "go to next marker" it would be "go to next marker of this name" and/or "go to the next marker that contains this text as part of the marker name".) Thoughts?
Linked from:
- samuel henriques @samuel_henriques
Hey @Jeremy_Bowker,
This will jump to the next marker containing "yadda" on marker's name.
It only works if you'r using sort by time, since it's looking for the next marker relative to the timeline location.here you go:
sf.ui.proTools.appActivate(); function setValue(mainCounterValue) { sf.ui.proTools.mainCounterSetValue({ targetValue: mainCounterValue }) } function reSetMainCounter(mainCounter) { /// match Bars Beats . mainCounter.match(/\|/) ? setValue("Bars|Beats") : null // match Min Secs . mainCounter.match(/\d+:\d+\.+\d/) ? setValue("Min:Secs") : null // match Time code mainCounter.match(/\d+:\d+:\d+:\d/) ? setValue("Timecode") : null // match Feet+Frames mainCounter.match(/\+/) ? setValue("Feet+Frames") : null // match Samples. mainCounter.match(/[^0-9]/) == null ? setValue("Samples") : null } function findLocationNameMatch(locationName) { const mainCounter = sf.ui.proTools.getCurrentTimecode().stringValue sf.ui.proTools.mainCounterSetValue({ targetValue: "Samples" }) const userSelection = sf.ui.proTools.selectionGetInSamples() const memoryLocations = sf.proTools.memoryLocationsFetchFromGui().collection.list.filter(x => x.mainCounterValue > userSelection.selectionStart && x.name.match(locationName)); try { sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: memoryLocations[0].number }); } catch (err) { log(`End of location markers containing:\n${locationName}`) } reSetMainCounter(mainCounter) } findLocationNameMatch("yadda")
- JJeremy Bowker @Jeremy_Bowker
@samuel_henriques this is fantastic! Thank you for sharing.
I wonder if there could be two commands:
- To define a global variable or at least store in a clipboard to determine the desired text. (Side question: Does SF have it's own clipboard that wouldn't be changed by clipboard changes in PT? And do global variables exist in SF?)
- To use the text that has already been defined (to sequentially go thru each of the markers with the same name).
Here's a work in progress of "1." (It's just what you did with the addition of a prompt for the text we're searching for.)
sf.ui.proTools.appActivate(); function setValue(mainCounterValue) { sf.ui.proTools.mainCounterSetValue({ targetValue: mainCounterValue }) } function reSetMainCounter(mainCounter) { /// match Bars Beats . mainCounter.match(/\|/) ? setValue("Bars|Beats") : null // match Min Secs . mainCounter.match(/\d+:\d+\.+\d/) ? setValue("Min:Secs") : null // match Time code mainCounter.match(/\d+:\d+:\d+:\d/) ? setValue("Timecode") : null // match Feet+Frames mainCounter.match(/\+/) ? setValue("Feet+Frames") : null // match Samples. mainCounter.match(/[^0-9]/) == null ? setValue("Samples") : null } function findLocationNameMatch(locationName) { const mainCounter = sf.ui.proTools.getCurrentTimecode().stringValue sf.ui.proTools.mainCounterSetValue({ targetValue: "Samples" }) const userSelection = sf.ui.proTools.selectionGetInSamples() const memoryLocations = sf.proTools.memoryLocationsFetchFromGui().collection.list.filter(x => x.mainCounterValue > userSelection.selectionStart && x.name.match(locationName)); try { sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: memoryLocations[0].number }); } catch (err) { log(`End of location markers containing:\n${locationName}`) } reSetMainCounter(mainCounter) } var markerName = prompt(`Please Enter Marker Name`); sf.clipboard.setText({ text: markerName }); findLocationNameMatch(markerName)
samuel henriques @samuel_henriques
cool, happy to help.
Are you thinking about something like ctrl+script to get a prompt and then use script with the saved word?- JJeremy Bowker @Jeremy_Bowker
Yes! Exactly. I.e. you're prompted & then type "cut" and then you can cycle thru all of the markers named "cut". And then type "VFX update" and then cycle thru all of the VFX update markers. (It'd be great to easliy change the text that you're looking for.)
samuel henriques @samuel_henriques
I'm pretty sure you are going to ask next how to go to the previous marker😂😂
samuel henriques @samuel_henriques
Just made it work backwards, might be two cool buttons next & previous with the same
globalState
. let me know how it's workingsamuel henriques @samuel_henriques
BTW if you only use timecode on main counter, I think I can make the script without having to change main counter to samples, removing some steps to get more speed.
- In reply tosamuel_henriques⬆:JJeremy Bowker @Jeremy_Bowker
Very exciting! Which is the most up to date one that I should try out? Thanks again!
- In reply tosamuel_henriques⬆:JJeremy Bowker @Jeremy_Bowker
Haha! You read my mind
samuel henriques @samuel_henriques
So... This is go to next memory location:
function setValue(mainCounterValue) { sf.ui.proTools.mainCounterSetValue({ targetValue: mainCounterValue }) } function reSetMainCounter(mainCounter) { /// match Bars Beats . mainCounter.match(/\|/) ? setValue("Bars|Beats") : null // match Min Secs . mainCounter.match(/\d+:\d+\.+\d/) ? setValue("Min:Secs") : null // match Time code mainCounter.match(/\d+:\d+:\d+:\d/) ? setValue("Timecode") : null // match Feet+Frames mainCounter.match(/\+/) ? setValue("Feet+Frames") : null // match Samples. mainCounter.match(/[^0-9]/) == null ? setValue("Samples") : null } function goToNextMemoryLocation(locationName) { const mainCounter = sf.ui.proTools.getCurrentTimecode().stringValue sf.ui.proTools.mainCounterSetValue({ targetValue: "Samples" }) const userSelection = sf.ui.proTools.selectionGetInSamples() const memoryLocations = sf.proTools.memoryLocationsFetchFromGui().collection['list'].filter(x => x.mainCounterValue > userSelection.selectionStart && x.name.match(locationName)); try { sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: memoryLocations[0].number }); } catch (err) { log(`End of location markers containing:\n${locationName}`) } reSetMainCounter(mainCounter) } sf.ui.proTools.appActivate(); const ctrlIsEnabled = event.keyboardState.hasControl if (globalState.markerName == undefined || ctrlIsEnabled) { globalState.markerName = prompt(`Please Enter Marker Name`); } else { goToNextMemoryLocation(globalState.markerName) }
this goes to previous memory location:
function setValue(mainCounterValue) { sf.ui.proTools.mainCounterSetValue({ targetValue: mainCounterValue }) } function reSetMainCounter(mainCounter) { /// match Bars Beats . mainCounter.match(/\|/) ? setValue("Bars|Beats") : null // match Min Secs . mainCounter.match(/\d+:\d+\.+\d/) ? setValue("Min:Secs") : null // match Time code mainCounter.match(/\d+:\d+:\d+:\d/) ? setValue("Timecode") : null // match Feet+Frames mainCounter.match(/\+/) ? setValue("Feet+Frames") : null // match Samples. mainCounter.match(/[^0-9]/) == null ? setValue("Samples") : null } function goToPreviousMemoryLocator(locationName) { const mainCounter = sf.ui.proTools.getCurrentTimecode().stringValue sf.ui.proTools.mainCounterSetValue({ targetValue: "Samples" }) const userSelection = sf.ui.proTools.selectionGetInSamples() const memoryLocations = sf.proTools.memoryLocationsFetchFromGui().collection['list'].filter(x => x.mainCounterValue < userSelection.selectionStart && x.name.match(locationName)).reverse(); try { sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: memoryLocations[0].number }); } catch (err) { log(`End of location markers containing:\n${locationName}`) } reSetMainCounter(mainCounter) } sf.ui.proTools.appActivate(); const ctrlIsEnabled = event.keyboardState.hasControl if (globalState.markerName == undefined || ctrlIsEnabled) { globalState.markerName = prompt(`Please Enter Marker Name`); } else { goToPreviousMemoryLocator(globalState.markerName) }
i'm still working on a version that doesn't need to change te main counter, I'm sure I'm not seeing something properly, but I'll get there.
let me know how these are working for you
- JJeremy Bowker @Jeremy_Bowker
This is so impressive! Yeah, it's working like a charm. Thank you!
samuel henriques @samuel_henriques
Thank you it's been great fun! Thank you to Master @Kitch for the help with this one, I got really stuck with my scripting bases.
here you go to NEXT:
function getNextMatchedMemoryLocation(locationName) { let mainCounter = sf.ui.proTools.getCurrentTimecode().stringValue // if main counter is bars beats, remove last three digits, since memory locations list ignores them mainCounter.match(/\|/) ? mainCounter = mainCounter.slice(0, -3) : null let cleanMainCounter = Number(mainCounter.replace(/[^0-9]/g, '').trim()) const memoryLocations = sf.proTools.memoryLocationsFetch().collection["list"].filter(x => Number(x.mainCounterValue.replace(/[^0-9]/g, '').trim()) > cleanMainCounter && x.name.match(locationName) ); try { sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: memoryLocations[0].number }); } catch (err) { log(`End of location markers containing:\n${locationName}`) } } sf.ui.proTools.appActivate(); const ctrlIsEnabled = event.keyboardState.hasControl if (globalState.markerName == undefined || ctrlIsEnabled) { globalState.markerName = prompt(`Please Enter Marker Name`); } else { getNextMatchedMemoryLocation(globalState.markerName) }
and go to previous:
function getPreviousMatchedMemoryLocation(locationName) { let mainCounter = sf.ui.proTools.getCurrentTimecode().stringValue // if main counter is bars beats, remove last three digits, since memory locations list ignores them mainCounter.match(/\|/) ? mainCounter = mainCounter.slice(0, -3) : null let cleanMainCounter = Number(mainCounter.replace(/[^0-9]/g, '').trim()) const memoryLocations = sf.proTools.memoryLocationsFetch().collection["list"].filter(x => Number(x.mainCounterValue.replace(/[^0-9]/g, '').trim()) < cleanMainCounter && x.name.match(locationName) ).reverse(); try { sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: memoryLocations[0].number }); } catch (err) { log(`End of location markers containing:\n${locationName}`) } } sf.ui.proTools.appActivate(); const ctrlIsEnabled = event.keyboardState.hasControl if (globalState.markerName == undefined || ctrlIsEnabled) { globalState.markerName = prompt(`Please Enter Marker Name`); } else { getPreviousMatchedMemoryLocation(globalState.markerName) }
Let me know how it goes.
- JJeremy Bowker @Jeremy_Bowker
Yup! Both work very well. So great!
- In reply tosamuel_henriques⬆:
samuel henriques @samuel_henriques
here you go, just replace the line:
findLocationNameMatch(markerName)
with:
const ctrlIsEnabled = event.keyboardState.hasControl if (globalState.markerName == undefined || ctrlIsEnabled) { globalState.markerName = prompt(`Please Enter Marker Name`); } else { findLocationNameMatch(globalState.markerName) }
- be aware, global state will be cleared id you restart soundFlow.
- if you are using a keyboard shortcut for this you need to assign both the shortcut and the shortcut+ctrl to be able to use this functionality. If you are using a surface, you don't need this, just press ctrl and the surface button.
hope this helps
samuel henriques @samuel_henriques
I'm working on a version that doesn't need to change the main counter, but ran into some problems. But it works well if you only use timecode, if you want to try its here:
- JJeremy Bowker @Jeremy_Bowker
This has been an amazing tool. Thanks again. I'm running into issues with this not working with PT 2023.12.0. Please let me know if you have any brilliant ideas.
All the best,
Jeremysamuel henriques @samuel_henriques
Hello @Jeremy_Bowker here's a version for PT 2023.12. The other scripts should be working soon.
function getMemoryLocationsList() { let memLocList = [] sf.ui.proTools.memoryLocationsEnsureWindow({ action: () => { const memLocWin = sf.ui.proTools.memoryLocationsWindow.tables.whoseTitle.is("Memory Locations") const memLocColumns = memLocWin.first.children.whoseRole.is("AXColumn").first.children.whoseRole.is("AXRow").first.children.whoseRole.is("AXCell").map(mem => mem.children.whoseRole.is("AXStaticText").first.title.value .replace("Numeration", "Number") .replace("Marker Name", "Name") .replace("Main Counter", "MainCounterValue") ); memLocWin.allItems[1].children.whoseRole.is("AXRow").map(r => { let obj = {}; r.children.whoseRole.is("AXCell").map((cell, i) => { obj["isSelected"] = false const cellValue = cell.children.whoseRole.is("AXStaticText").first.value.value if (cellValue.startsWith("Selected. ")) { obj["isSelected"] = true } obj[memLocColumns[i]] = cellValue.replace("Selected. ", "") }) memLocList.push(obj) }) }, restoreWindowOpenState: true }) return memLocList }; /** * @param {'Previous'|'Next'} direction * @param {string} locationName */ function getMatchedMemoryLocation(locationName, direction = 'Next') { let mainCounter = sf.ui.proTools.getCurrentTimecode().stringValue // if main counter is bars beats, remove last three digits, since memory locations list ignores them mainCounter.match(/\|/) ? mainCounter = mainCounter.slice(0, -3) : null let cleanMainCounter = Number(mainCounter.replace(/[^0-9]/g, '').trim()) let memoryLocations if (direction === "Previous") { memoryLocations = getMemoryLocationsList().filter(memLoc => Number(memLoc['MainCounterValue'].replace(/[^0-9]/g, '').trim()) < cleanMainCounter && memLoc['Name'].toLowerCase().includes(locationName.toLowerCase()) ).reverse() } else { memoryLocations = getMemoryLocationsList().filter(memLoc => Number(memLoc['MainCounterValue'].replace(/[^0-9]/g, '').trim()) > cleanMainCounter && memLoc['Name'].toLowerCase().includes(locationName.toLowerCase()) ) } try { sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: +memoryLocations[0]['Number'] }); } catch (err) { log(`End of location markers containing:\n${locationName}`) } } sf.ui.proTools.appActivate(); const ctrlIsEnabled = event.keyboardState.hasControl if (globalState.markerName == undefined || ctrlIsEnabled) { globalState.markerName = prompt(`Please Enter Marker Name`); } else { getMatchedMemoryLocation(globalState.markerName) }
This by default will go to the next memory locator
to go to previous replace the last line
getMatchedMemoryLocation(globalState.markerName)
with
getMatchedMemoryLocation(globalState.markerName, 'Previous')
- JJeremy Bowker @Jeremy_Bowker
Ammmmazing!!! Thank you!!! Trying now
- JIn reply toJeremy_Bowker⬆:Jeremy Bowker @Jeremy_Bowker
Hi @samuel_henriques !
This script is SO helpful. Thanks again.
With PT 2024.10.2 I'm now getting an error and the log says "TypeError: Cannot read property 'replace' of undefined". Any thoughts as to what this is?
Thanks for your time!
Jeremysamuel henriques @samuel_henriques
Hello Jeremy,
No problem here also on PT 2024.10.2. and Sequoia 15.4
But I'm noticing pro tools is getting some personality so I keep quitting to fix some issues.
Is this constant on every session?- JJeremy Bowker @Jeremy_Bowker
Hi!
I've restarted and tried other sessions and the problem persists. I'll keep investigating. Thanks for your fast reply.
-Jeremy
- JIn reply toJeremy_Bowker⬆:Jeremy Bowker @Jeremy_Bowker
While this is happening, the memory locations window opens and closes extremely fast. I wonder if a "wait" is necessary somewhere (on this computer)...
samuel henriques @samuel_henriques
Mine does the same if its closed. You get same problem if its open?
Try to send a screen recording while its failing, I might spot something