scroll to track
Hi @Kitch how to automate Scroll to Track, the scenario:
- My Mix sessions are pretty organize over a pretty stable template where most food groups are contained in routing folder groups that names does not change over the time, ex: cDX A CHAIN, cFX A CHAIN, etc... I would like to create a series of macros, that would be mapped to stream deck buttons to easily jump around the session, using the "Scroll to Track" to jump acros those "anchor" points, i.e. the Routing Folders.
What I tried using macros:
1º Step: Click Menu item: Track > Scroll to Track
2º Step: Wait to Ui Element: Appear > Pro Tools Confirmation Dialog
3º Step: I try to enter track names in to the text field and here nothings happens
There is "Text Field" and it seems to not be accessible by macros...
I try several commands, like "Open & Select item in pop up menu" or "Set Value of Text Area", but none of them seems to work...
There is any way to do that?
Many thanks
- Chris Shaw @Chris_Shaw2022-07-20 20:55:17.846Z
Ricardo,
I use this function quite a bit with my packages.
Just just change the value oftrackName
to the name of the track you wish to scroll to.
You can also use an array of track names and the function will scroll to the first track in the array.// `trackName` is the name of the track to be scrolled to. // A single track name or an array of track names can be used // If an array is used then the function will scroll to the first name in the array const trackName = "cDX A CHAIN" function csScrollToTrack(trackName) { var confirmationDialogWin = sf.ui.proTools.confirmationDialog; var scrollTrack = (typeof trackName == "object") ? trackName[0] : trackName; try { sf.ui.proTools.menuClick({ menuPath: ['Track', 'Scroll to Track...'] }); confirmationDialogWin.elementWaitFor({ timeout: 100 }); } catch (err) { sf.keyboard.press({ keys: "n", repetitions: 2 }); sf.ui.proTools.menuClick({ menuPath: ['Track', 'Scroll to Track...'] }); confirmationDialogWin.elementWaitFor({}, `Could not find Scroll to track Dialog`); } confirmationDialogWin.textFields.first.elementSetTextFieldWithAreaValue({ value: scrollTrack, useMouseKeyboard: true }); confirmationDialogWin.buttons.whoseTitle.is('OK').first.elementClick(); confirmationDialogWin.elementWaitFor({ waitType: 'Disappear' }); }; ///////////// // M A I N // ///////////// csScrollToTrack(trackName)
- RRicardo Cutz @Ricardo_Cutz
thanks! I don't know why it is not working, nothing happens...
I copy everything, the only change I made, and I understand that it is what I need to change is in the first lineconst trackName = "cDX A CHAIN"
I change the name of the track correct? Or should I change after the MAIN?
Chris Shaw @Chris_Shaw2022-07-20 21:46:33.958Z
Yes, you only need to change the name of the track (
trackName =
)
Hmm, it's working on my system.
Which versions of PT and SF are you using?- RRicardo Cutz @Ricardo_Cutz
PT 2022.6, MacOs Catalina e SF 5.1.5
- In reply toChris_Shaw⬆:
Jack Green @Jack_Green
I use something similar to this all over the place in several scripts.
Im just wondering if there is a way to use error handling to output the trackName is attempted to scroll to in the event of a failure ?
Thanks !
Chris Shaw @Chris_Shaw2024-07-23 15:55:49.164Z
There a two ways to do this. With most SF commands you can add an error message like in line 30 here:
// `trackName` is the name of the track to be scrolled to. // A single track name or an array of track names can be used // If an array is used then the function will scroll to the first name in the array const trackName = "Audio 14" function csScrollToTrack(trackName) { var confirmationDialogWin = sf.ui.proTools.confirmationDialog; var scrollTrack = (typeof trackName == "object") ? trackName[0] : trackName; try { sf.ui.proTools.menuClick({ menuPath: ['Track', 'Scroll to Track...'] }); confirmationDialogWin.elementWaitFor({ timeout: 100 }); } catch (err) { sf.keyboard.press({ keys: "n", repetitions: 2 }); sf.ui.proTools.menuClick({ menuPath: ['Track', 'Scroll to Track...'] }); confirmationDialogWin.elementWaitFor({}, `Could not find Scroll to track Dialog`); } confirmationDialogWin.textFields.first.elementSetTextFieldWithAreaValue({ value: scrollTrack, useMouseKeyboard: true }); confirmationDialogWin.buttons.whoseTitle.is('OK').first.elementClick(); confirmationDialogWin.elementWaitFor({ waitType: 'Disappear' },`Could not find track named ${trackName}`); }; ///////////// // M A I N // ///////////// csScrollToTrack(trackName)
The issue here is that the track scroll window will stay open.
To get around this you can add a second try/catch block around the confirmation dialog "OK" button press that will notify the track name then dismiss the scroll to track window with two esc key presses:// `trackName` is the name of the track to be scrolled to. // A single track name or an array of track names can be used // If an array is used then the function will scroll to the first name in the array const trackName = "Audio 14" function csScrollToTrack(trackName) { var confirmationDialogWin = sf.ui.proTools.confirmationDialog; var scrollTrack = (typeof trackName == "object") ? trackName[0] : trackName; try { sf.ui.proTools.menuClick({ menuPath: ['Track', 'Scroll to Track...'] }); confirmationDialogWin.elementWaitFor({ timeout: 100 }); } catch (err) { sf.keyboard.press({ keys: "n", repetitions: 2 }); sf.ui.proTools.menuClick({ menuPath: ['Track', 'Scroll to Track...'] }); confirmationDialogWin.elementWaitFor({}, `Could not find Scroll to track Dialog`); } confirmationDialogWin.textFields.first.elementSetTextFieldWithAreaValue({ value: scrollTrack, useMouseKeyboard: true }); confirmationDialogWin.buttons.whoseTitle.is('OK').first.elementClick(); try{ confirmationDialogWin.elementWaitFor({ waitType: 'Disappear' }); }catch(err){ sf.interaction.notify({ title:`Could not find track "${trackName}"` }); sf.keyboard.press({keys:"esc", repetitions:2}) } }; ///////////// // M A I N // ///////////// csScrollToTrack(trackName)
- In reply toChris_Shaw⬆:
Ian Bodzasi @Ian_Bodzasi
Hi Chris. Would there be a simple way to alter this to search for a match from a list of possible track names? For example, if I want to scroll to the drums it might be called Drums or it might be called Kit. A B3 might be B3 or it might be Organ, etc.
Chris Shaw @Chris_Shaw2024-08-01 15:26:01.566Z2024-08-01 18:21:01.737Z
This should do it. It will scroll to the first name it finds.
Read the comments to customize it how you wish:/* Edit the `trackNames` object as follows: - trackType - this is a general name for the track type. It's used to display error messages (line 21) - possibleNames - theses are the names that you are searching for */ const trackNames = { trackType: "Drums", possibleNames: ["Drums", "Kit"] } /** * @param {object} trackNames * @param {string} trackNames.trackType * @param {string[]} trackNames.possibleNames */ function findMatchingTrackName(trackNames) { const { trackType, possibleNames } = trackNames; const sessionTracks = sf.ui.proTools.visibleTrackNames const matchingTracks = sessionTracks.filter(t => possibleNames.includes(t)); if (matchingTracks.length == 0) { sf.interaction.notify({ title: `No matching tracks were found for ${trackType}` }) throw 0 } return (matchingTracks[0]) } function csScrollToTrack(trackName) { var confirmationDialogWin = sf.ui.proTools.confirmationDialog; var scrollTrack = (typeof trackName == "object") ? trackName[0] : trackName; try { sf.ui.proTools.menuClick({ menuPath: ['Track', 'Scroll to Track...'] }); confirmationDialogWin.elementWaitFor({ timeout: 100 }); } catch (err) { sf.keyboard.press({ keys: "n", repetitions: 2 }); sf.ui.proTools.menuClick({ menuPath: ['Track', 'Scroll to Track...'] }); confirmationDialogWin.elementWaitFor({}, `Could not find Scroll to track Dialog`); } confirmationDialogWin.textFields.first.elementSetTextFieldWithAreaValue({ value: scrollTrack, useMouseKeyboard: true }); confirmationDialogWin.buttons.whoseTitle.is('OK').first.elementClick(); try { confirmationDialogWin.elementWaitFor({ waitType: 'Disappear' }); } catch (err) { sf.interaction.notify({ title: `Could not find track "${trackName}"` }); sf.keyboard.press({ keys: "esc", repetitions: 2 }) } }; ///////////// // M A I N // ///////////// const trackToScrollTo = findMatchingTrackName(trackNames) csScrollToTrack(trackToScrollTo)
Ian Bodzasi @Ian_Bodzasi
Thanks Chris! I'm getting the follow error back when running it...
01.08.2024 12:48:09.26 [EditorWindow:Renderer]: Active Focus Container: commandsPage/folders Line 33963 file:///Applications/SoundFlow.app/Contents/Helpers/SoundFlow.app/Contents/Resources/app.asar/dist/editor.js
01.08.2024 12:48:11.91 [Backend]: #StreamDeck: deck-button-click -> Go to DrumsCommand: Go to Drums [user:cky4td7z60002qk10y9y2hr59:clzbi6xx40000zm10skfjyofo]
01.08.2024 12:48:11.95 [Backend]: JavaScript error with InnerException: Newtonsoft.Json.JsonSerializationException: Error converting value "AudioTrack" to type 'SoundFlow.Shortcuts.Automation.Actions.PtAppTrackType'. Path 'track_list[0].type', line 10, position 25.
---> System.ArgumentException: Requested value 'AudioTrack' was not found.
at Newtonsoft.Json.Utilities.EnumUtils.ParseEnum(Type enumType, NamingStrategy namingStrategy, String value, Boolean disallowNumber) + 0x4e0
at Newtonsoft.Json.Converters.StringEnumConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer) + 0xfc
--- End of inner exception stack trace ---
at Newtonsoft.Json.Converters.StringEnumConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer) + 0x2e0
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue) + 0x148
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target) + 0x11c
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id) + 0x77c
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) + 0x2e4
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) + 0xb8
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IList list, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id) + 0x36c
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id) + 0x194
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) + 0xdc
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target) + 0x158
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id) + 0x77c
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) + 0x2e4
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) + 0xb8
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) + 0x288
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) + 0x100
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings) + 0x94
at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings) + 0x38
at SoundFlow.Shortcuts.Ax.AxNodes.PtTrackList.d__5.MoveNext() + 0x250
--- End of stack trace from previous location ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x24
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task) + 0x100
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task, ConfigureAwaitOptions) + 0x68
at SoundFlow.Shortcuts.Ax.AxNodes.PtAppList1.<GetItems>d__8.MoveNext() + 0xdc --- End of stack trace from previous location --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x24 at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task) + 0x100 at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task, ConfigureAwaitOptions) + 0x68 at SoundFlow.Shortcuts.Ax.AxNodes.PtAppList
1.<>c__DisplayClass6_0.<<get_allItems>b__0>d.MoveNext() + 0xcc
--- End of stack trace from previous location ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x24
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task) + 0x100
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task, ConfigureAwaitOptions) + 0x68
at Nito.AsyncEx.Synchronous.TaskExtensions.WaitAndUnwrapException[TResult](Task1 task) + 0x44 at System.Threading.Tasks.ContinuationResultTaskFromResultTask
2.InnerInvoke() + 0x54
at System.Threading.ExecutionContext.RunInternal(ExecutionContext, ContextCallback, Object) + 0x8c
--- End of stack trace from previous location ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x24
at System.Threading.ExecutionContext.RunInternal(ExecutionContext, ContextCallback, Object) + 0xdc
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task&, Thread) + 0xa0
--- End of stack trace from previous location ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x24
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task) + 0x100
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task, ConfigureAwaitOptions) + 0x68
at Nito.AsyncEx.Synchronous.TaskExtensions.WaitAndUnwrapException[TResult](Task1 task) + 0x44 at Nito.AsyncEx.AsyncContext.Run[TResult](Func
1 action) + 0xe0
at sfbackend!+0x282a0f0
at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0xcc
!! Command Error: Go to Drums [user:cky4td7z60002qk10y9y2hr59:clzbi6xx40000zm10skfjyofo]:
Error: Error converting value "AudioTrack" to type 'SoundFlow.Shortcuts.Automation.Actions.PtAppTrackType'. Path 'track_list[0].type', line 10, position 25.
(Go to Drums line 22)01.08.2024 12:48:11.95 [Backend]: << Command: Go to Drums [user:cky4td7z60002qk10y9y2hr59:clzbi6xx40000zm10skfjyofo]
Chris Shaw @Chris_Shaw2024-08-01 18:17:08.254Z
Which version of PT are you running?
Chris Shaw @Chris_Shaw2024-08-01 18:21:31.537Z
I re-edited the code above - give it a try.
Ian Bodzasi @Ian_Bodzasi
That's perfect, thanks so much Chris! I'm on 2023.6 here. Had some GUI issues with 2023.9, meters acting weird, etc, so I've been staying put where things are stable. New features are starting to add up though, it might be time for an update. Out of curiosity, what's the most recent version you're finding stable there?
Chris Shaw @Chris_Shaw2024-08-01 20:35:59.866Z
I'm doing fine with 2024.6 / Ventura