No internet connection
  1. Home
  2. Packages
  3. Marker CUE Button Creator

Lag when pressing Stream Deck Advanced Locations

By Peter Gallacher @Peter_Gallacher
    2022-04-27 08:11:15.694Z

    Title

    Lag when pressing Stream Deck Advanced Locations

    What do you expect to happen when you run the script/macro?

    I currently have three presets I've set up within the Advanced Memory Locations Template. CLICK, NOISE and POP. Whenever I press these on my Stream Deck within big session they are very slow to load within Pro Tools. I have a lag of around 10/12 seconds.

    However, if I add a memory location manaully it's super fast. The problem does not persist in smaller sessions.

    Thanks in advance
    Peter

    Are you seeing an error?

    What happens when you run this script?

    When I press the button on Stream Deck to activate the macros, it's as if I haven't pressed anything. There's a delay of around 10/12 seconds before the memory location appears.

    How were you running this script?

    I used a Stream Deck button

    How important is this issue to you?

    5

    Details

    {
        "inputExpected": "I currently have three presets I've set up within the Advanced Memory Locations Template. CLICK, NOISE and POP. Whenever I press these on my Stream Deck within big session they are very slow to load within Pro Tools. I have a lag of around 10/12 seconds.\n\nHowever, if I add a memory location manaully it's super fast. The problem does not persist in smaller sessions.\n\nThanks in advance\nPeter",
        "inputIsError": false,
        "inputWhatHappens": "When I press the button on Stream Deck to activate the macros, it's as if I haven't pressed anything. There's a delay of around 10/12 seconds before the memory location appears.",
        "inputHowRun": {
            "key": "-MpfwmPg-2Sb-HxHQAff",
            "title": "I used a Stream Deck button"
        },
        "inputImportance": 5,
        "inputTitle": "Lag when pressing Stream Deck Advanced Locations"
    }

    Source

    const { cueText } = event.props;
    const isMemLocWinOpened = sf.ui.proTools.getMenuItem('Window', 'Memory Locations').isMenuChecked
    const memoryLocations = sf.proTools.memoryLocationsFetchFromGui().collection.list.filter(x => x.name.match(cueText))
    const modifierState = event.keyboardState.asString
    
    /**
     * @param {object} obj
     * @param {string} obj.name
     */
    function createMemoryLocationsMarker({ name }) {
    
        sf.ui.proTools.appActivateMainWindow();
        const dlg = sf.ui.proTools.newMemoryLocationDialog;
        if (!dlg.exists) {
            sf.keyboard.press({ keys: "numpad enter" });
        }
    
        dlg.elementWaitFor();
        dlg.radioButtons.whoseTitle.is("Marker").first.elementClick();
        dlg.textFields.allItems[1].elementSetTextFieldWithAreaValue({
            value: name,
        });
    
        dlg.buttons.whoseTitle.is("OK").first.elementClick();
        dlg.elementWaitFor({ waitType: "Disappear", });
    
    }
    
    function paste(memoryLocations) {
    
        sf.ui.proTools.memoryLocationsGoto({
            memoryLocationNumber: memoryLocations.number
        })
        sf.ui.proTools.menuClick({ menuPath: ["Edit", "Paste"] })
    }
    
    function clearMemoryLocations(name) {
    
    
        // //  Open mem loc win
        sf.ui.proTools.memoryLocationsShowWindow()
    
        // Get Memory Locations including...
        const memoryLocations = sf.proTools.memoryLocationsFetch().collection['List'].filter(m => m.name.includes(name))
    
        // Clear found
        memoryLocations.forEach(memLoc => {
            sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: memLoc.number })
            sf.ui.proTools.memoryLocationsWindow.popupButtons.first.popupMenuSelect({ menuPath: ['Clear*'], useWildcards: true });
        });
    
        ///alert(`Cleared All Memory Locations Names Including: \n\n                       - ${name} -`)
    
    };
    
    function goToNextMatchedMemoryLocation(locationName) {
        let currentTimecode = sf.ui.proTools.getCurrentTimecode().stringValue;
    
        //  if main counter is bars beats, remove last three digits, since memory locations list ignores them
        currentTimecode.match(/\|/) ? currentTimecode = currentTimecode.slice(0, -3) : null;
    
        let cleanMainCounter = Number(currentTimecode.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}`);
        }
    }
    
    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}`) }
    }
    
    /** Counts the amount of times this function is called during a fixed amount of time 
     * and passes that value to a callback from which different actions can take place.
     * @param {object} args
     * @param {string} args.name - Name for this instance.
     * @param {number} args.waitTime - Amount of time to wait in milliseconds. 
     * @param {function} args.action - Callback to execute when the waitTime is over. Counter value will be passed as an argument.
     */
    function actionCounter({ name, waitTime, action }) {
        let actionCounter;
    
        if (!globalState.actionCounter) globalState.actionCounter = {};
        actionCounter = globalState.actionCounter;
    
        if (!actionCounter[name]) {
            actionCounter[name] = {
                dateMs: (new Date).valueOf(),
                i: 1
            };
        } else {
            actionCounter[name].i++
            return;
        }
    
        sf.engine.runInBackground(() => {
            sf.wait({ intervalMs: waitTime, executionMode: "Background" });
            try {
                action(actionCounter[name].i);
            } finally {
                delete globalState.actionCounter
            }
        });
    }
    
    function main() {
        
        if (globalState.toggleCreateOrGoTo == undefined || globalState.toggleCreateOrGoTo === true) {
    
            switch (modifierState) {
                case "cmd":
                    memoryLocations.forEach(paste)
                    break
                case "":
                    createMemoryLocationsMarker({
                        name: cueText,
                    });
                    break
                case "shift":
                    clearMemoryLocations(cueText)
                    break
            };
        }
    
        if (globalState.toggleCreateOrGoTo === false) {
            switch (modifierState) {
                case "cmd":
                    memoryLocations.forEach(paste)
                    break
                case "":
                    actionCounter({
                        name: 'memoryLocaitonFowardorBackwards',
                        waitTime: 500,
                        action: i => {
                            switch (i) {
                                case 1:
                                    goToNextMatchedMemoryLocation(cueText)
                                    break;
                                case 2:
                                    getPreviousMatchedMemoryLocation(cueText)
                                    break;
                            }
                        }
                    });
                    break
                case "shift":
                    clearMemoryLocations(cueText)
                    break
            };
        }
        // Set initial state of Mem Loc Win
        if (!isMemLocWinOpened) {
            sf.ui.proTools.menuClick({ menuPath: ['Window', 'Memory Locations'], targetValue: "Disable" })
        };
    }
    
    main()
    
    

    Links

    User UID: Xm0t8L8vbddwUSfY4G9a7IsOZqt2

    Feedback Key: sffeedback:Xm0t8L8vbddwUSfY4G9a7IsOZqt2:-N0e3kwp7dhu2U_q8JiZ

    Feedback ZIP

    • 25 replies

    There are 25 replies. Estimated reading time: 33 minutes

    1. S
      SoundFlow Bot @soundflowbot
        2022-04-27 08:11:18.513Z

        Thanks for posting a question or an issue related to the 'Marker CUE Button Creator' package.
        This package is made by @Owen_Granich_Young. We're auto-tagging them here so that they will hopefully be able to help you.

        1. In reply toPeter_Gallacher:
          Kitch Membery @Kitch2022-05-03 01:50:08.771Z2022-05-03 16:43:22.095Z

          @Peter_Gallacher & @Owen_Granich_Young...

          OK... So this is untested, but I finally got around to doing a bit of a refactor;

          • I Removed multiple times that the Memory Locations window was being scraped (as it only needs to happen once).
          • Removed a duplicate way the Memory locations were being scraped (there were two different methods being used).
          • I combined the navigate to markers functions as there was a bunch of duplicate code.
          • I renamed some of the functions and variables as their names did not match what they were doing.

          It's not perfect but hopefully it will speed the script up on large sessions.

          Let me know how it goes. Or if it even works still hahahhaha!?!?!?!??!!

          Be sure to save your original script before overwriting just in case :-)

          Here it is;

          const { cueText } = event.props;
          
          const isMemLocWinOpened = sf.ui.proTools.getMenuItem('Window', 'Memory Locations').isMenuChecked;
          const memoryLocations = sf.proTools.memoryLocationsFetch().collection['List'];
          const matchingMemoryLocations = memoryLocations.filter(ml => ml.name === (cueText));
          const modifierState = event.keyboardState.asString;
          
          /**
           * @param {object} obj
           * @param {string} obj.name
           */
          function createMemoryLocationsMarker({ name }) {
              sf.ui.proTools.appActivateMainWindow();
          
              const dlg = sf.ui.proTools.newMemoryLocationDialog;
              
              if (!dlg.exists) {
                  sf.keyboard.press({ keys: "numpad enter" });
              }
          
              dlg.elementWaitFor();
          
              dlg.radioButtons.whoseTitle.is("Marker").first.elementClick();
          
              dlg.textFields.allItems[1].elementSetTextFieldWithAreaValue({
                  value: name,
              });
          
              dlg.buttons.whoseTitle.is("OK").first.elementClick();
          
              dlg.elementWaitFor({ waitType: "Disappear", });
          }
          
          function pasteAtMemoryLocation(memoryLocation) {
              sf.ui.proTools.memoryLocationsGoto({
                  memoryLocationNumber: memoryLocation.number,
              });
          
              sf.ui.proTools.menuClick({
                  menuPath: ["Edit", "Paste"],
              });
          }
          
          function clearMemoryLocations() {
              sf.ui.proTools.memoryLocationsShowWindow();
          
              memoryLocations.forEach(memLoc => {
                  sf.ui.proTools.memoryLocationsGoto({
                      memoryLocationNumber: memLoc.number,
                  });
          
                  sf.ui.proTools.memoryLocationsWindow.popupButtons.first.popupMenuSelect({
                      menuPath: ['Clear*'],
                      useWildcards: true,
                  });
              });
          };
          
          /**
           * @param {object} obj
           * @param {string} obj.locationName
           * @param {string} obj.direction
           */
          function goToMatchedMemoryLocation({ locationName, direction }) {
              let currentTimecode = sf.ui.proTools.getCurrentTimecode().stringValue;
          
              //If main counter is bars beats, remove last three digits, since memory locations list ignores them
              currentTimecode.match(/\|/) ? currentTimecode = currentTimecode.slice(0, -3) : null;
          
              let cleanMainCounter = Number(currentTimecode.replace(/[^0-9]/g, '').trim());
          
              let targetMemoryLocation;
          
              if (direction === "next") {
                  targetMemoryLocation = matchingMemoryLocations.filter(x =>
                      Number(x.mainCounterValue.replace(/[^0-9]/g, '').trim()) > cleanMainCounter
                  )[0].number;
              } else if (direction === "previous") {
                  targetMemoryLocation = matchingMemoryLocations.filter(x =>
                      Number(x.mainCounterValue.replace(/[^0-9]/g, '').trim()) < cleanMainCounter
                  ).reverse()[0].number;
              }
          
              try {
                  sf.ui.proTools.memoryLocationsGoto({
                      memoryLocationNumber: targetMemoryLocation,
                  });
              } catch (err) {
                  log(`There are no "${direction}"" location markers containing:\n"${locationName}"`);
              }
          }
          
          /** Counts the amount of times this function is called during a fixed amount of time 
           * and passes that value to a callback from which different actions can take place.
           * @param {object} args
           * @param {string} args.name - Name for this instance.
           * @param {number} args.waitTime - Amount of time to wait in milliseconds. 
           * @param {function} args.action - Callback to execute when the waitTime is over. Counter value will be passed as an argument.
           */
          function actionCounter({ name, waitTime, action }) {
              let actionCounter;
          
              if (!globalState.actionCounter) globalState.actionCounter = {};
              actionCounter = globalState.actionCounter;
          
              if (!actionCounter[name]) {
                  actionCounter[name] = {
                      dateMs: (new Date).valueOf(),
                      i: 1
                  };
              } else {
                  actionCounter[name].i++
                  return;
              }
          
              sf.engine.runInBackground(() => {
                  sf.wait({ intervalMs: waitTime, executionMode: "Background" });
                  try {
                      action(actionCounter[name].i);
                  } finally {
                      delete globalState.actionCounter;
                  }
              });
          }
          
          function main() {
              if (globalState.toggleCreateOrGoTo == undefined || globalState.toggleCreateOrGoTo === true) {
          
                  switch (modifierState) {
                      case "cmd":
                          memoryLocations.forEach(pasteAtMemoryLocation);
                          break
                      case "":
                          createMemoryLocationsMarker({
                              name: cueText,
                          });
                          break
                      case "shift":
                          clearMemoryLocations();
                          break
                  };
              }
          
              if (globalState.toggleCreateOrGoTo === false) {
                  switch (modifierState) {
                      case "cmd":
                          memoryLocations.forEach(pasteAtMemoryLocation);
                          break
                      case "":
                          actionCounter({
                              name: 'memoryLocaitonFowardorBackwards',
                              waitTime: 500,
                              action: i => {
                                  switch (i) {
                                      case 1:
                                          goToMatchedMemoryLocation({
                                              locationName: cueText,
                                              direction: "next"
                                          });
                                          break;
                                      case 2:
                                          goToMatchedMemoryLocation({
                                              locationName: cueText,
                                              direction: "previous"
                                          });
                                          break;
                                  }
                              }
                          });
                          break
                      case "shift":
                          clearMemoryLocations();
                          break
                  };
              }
              // Set initial state of Mem Loc Win
              if (!isMemLocWinOpened) {
          
                  sf.ui.proTools.menuClick({
                      menuPath: ['Window', 'Memory Locations'],
                      targetValue: "Disable",
                  });
          
              };
          }
          
          main();
          

          UPDATED - ADDING MARKER NO-LONGER DELETES EXISTING MARKER
          Rock on, champions!

          1. Peter Gallacher @Peter_Gallacher
              2022-05-03 14:50:24.928Z

              WOW! Thanks for this @Kitch I will wait to hear back from @Owen_Granich_Young as he may want to update the app. Then I can update the app on my side once he's done it. Hopefully speak soon, Peter

              1. Kickass boss! I gotta do some ADR this morning but I'll update this afternoon.

                You rock!
                Hope to see you tomorrow at webinar.

                bests,
                Owen

              2. In reply toKitch:

                Uhoh @Kitch dropping a marker removes the previous one.... so only one of any type of marker can exist however you coded it. OH MY... in fact on closer testing ONLY ONE MARKER at all can exist...

                Not sure what you did? I didn't get as far as checking the rest of the code as this part seems pretty function breaking.

                More soon,
                Owen

                1. Kitch Membery @Kitch2022-05-03 16:46:10.174Z

                  Ahhh yeah, I was not expecting that behavior from the updated createMemoryLocationsMarker function (The original code for that was fine.).... It should be fixed/restored in the above post now.

                  Rock on!

                  1. Nice! I'll give it a spin and then upload when I'm home.

                    1. Peter Gallacher @Peter_Gallacher
                        2022-05-03 17:14:22.006Z

                        Thanks @Owen_Granich_Young @Kitch I look forward to your updated tests Owen and an updated app in due course. Thanks again guys. Speak soon. Peter.

                  2. In reply toKitch:

                    Ok so for some reason, the Action Counter Functionality is broken, and I can't figure it out. So no matter if you press twice or once when you're in goTo mode it only goes to the next location, not the previous. I swapped them as an experiment and Previous does work as well, so I'm not 100% what's wrong. @raphaelsepulveda this Action Counter Double tap is your script I've placed many places and it's always worked. Any idea why it might not like working in this script?

                    Sadly my one ADR session today is at 11am so I'll miss webinar today @Kitch

                    more soon,
                    Owen

                    1. For reference Action Counter works perfectly well in this script.

                      function goToNextMatchedMemoryLocation(locationName) {
                          let currentTimecode = sf.ui.proTools.getCurrentTimecode().stringValue;
                      
                          //  if main counter is bars beats, remove last three digits, since memory locations list ignores them
                          currentTimecode.match(/\|/) ? currentTimecode = currentTimecode.slice(0, -3) : null;
                      
                          let cleanMainCounter = Number(currentTimecode.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}`);
                          }
                      }
                      
                      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}`) }
                      }
                      
                      /** Counts the amount of times this function is called during a fixed amount of time 
                       * and passes that value to a callback from which different actions can take place.
                       * @param {object} args
                       * @param {string} args.name - Name for this instance.
                       * @param {number} args.waitTime - Amount of time to wait in milliseconds. 
                       * @param {function} args.action - Callback to execute when the waitTime is over. Counter value will be passed as an argument.
                       */
                      function actionCounter({ name, waitTime, action }) {
                          let actionCounter;
                      
                          if (!globalState.actionCounter) globalState.actionCounter = {};
                          actionCounter = globalState.actionCounter;
                      
                          if (!actionCounter[name]) {
                              actionCounter[name] = {
                                  dateMs: (new Date).valueOf(),
                                  i: 1
                              };
                          } else {
                              actionCounter[name].i++
                              return;
                          }
                      
                          sf.engine.runInBackground(() => {
                              sf.wait({ intervalMs: waitTime, executionMode: "Background" });
                              try {
                                  action(actionCounter[name].i);
                              } finally {
                                  delete globalState.actionCounter
                              }
                          });
                      }
                      
                      function main() {
                          //Activate Pro Tools Main Window
                          sf.ui.proTools.appActivateMainWindow();
                      
                          //Get Control Modifier state    
                          const ctrlIsEnabled = event.keyboardState.hasControl;
                      
                          if (globalState.markerName == undefined || ctrlIsEnabled) {
                      
                              //Retrieve memory locations
                              let memoryLocations = sf.proTools.memoryLocationsFetch().collection['List'];
                      
                              let memoryLocationNames = memoryLocations.map(ml => ml["Name"]);
                      
                              //Filter out Memory location name duplicates
                              let uniqueMemoryLocationNames = [...new Set(memoryLocationNames)];
                      
                              //Popup Search
                              let targetMemoryLocation = sf.interaction.popupSearch({
                                  items: uniqueMemoryLocationNames.map(name => ({ name: name })),
                              }).item.name
                      
                              sf.ui.proTools.appActivateMainWindow();
                      
                              //Set the globalState marker name
                              globalState.markerName = targetMemoryLocation;
                      
                          } else {
                              actionCounter({
                                  name: 'memoryLocaitonFowardorBackwards',
                                  waitTime: 500,
                                  action: i => {
                                      switch (i) {
                                          case 1:
                                              goToNextMatchedMemoryLocation(globalState.markerName)
                                              break;
                                          case 2:
                                              getPreviousMatchedMemoryLocation(globalState.markerName)
                                              break;
                                      }
                                  }
                              });
                          }
                      }
                      
                      main();
                      
                      1. Kitch Membery @Kitch2022-05-04 16:17:33.624Z

                        I'll take a look when I get a chance... If it was working before then IT's probably something I've done.

                        Can you note down the rules to the functionality, so I can check it..
                        ie;
                        When I hold down "Command" I want it to do this...
                        When I hold down "Shift" I want it to do this...
                        When I hold down no modifiers I want it to do this...
                        When I hold down "Command" and trigger twice within the 500ms I want it to do this...
                        When I hold down "Shift" and trigger twice within the 500ms I want it to do this...
                        When I hold down no modifiers and trigger twice within the 500ms I want it to do this...

                        Thanks :-)
                        Rock on.

                        1. In Create Global State (all working as intended currently):
                          No Modifiers = Create Memory location with cueText (working as intended)
                          Hold Shift = Remove Memory Locations with cueText (working as intended)
                          Hold Cmd = Paste Clipboard to Memory Locations with cueText (working as intended)

                          In GoTo Global State
                          No Modifiers 1 press within 500ms = Go to next Memory location with cueText
                          No Modifiers 2 press within 500ms = Go to previous Memory location with cueText
                          (This currently only does the 1 press no matter what. I flipped them and both previous and next you have scripted do work correctly)

                          Hold Shift = Remove Memory Locations with cueText (working as intended)
                          Hold Cmd = Paste Clipboard to Memory Locations with cueText (working as intended)

                          Bests,
                          Owen

                          1. Kitch Membery @Kitch2022-05-05 21:32:17.185Z

                            Hi @Owen_Granich_Young,

                            So... I've modified the main function to simplify the modifier/mode combinations. (4 combinations in total)
                            I also realized that fetching the Memory locations was closing the Memory Locations window by default, which I believe may have been causing the issue you are experiencing.

                            const { cueText } = event.props;
                            
                            const isMemLocWinOpened = sf.ui.proTools.invalidate().memoryLocationsWindow.exists;
                            const memoryLocations = sf.proTools.memoryLocationsFetch({ restoreWindowOpenState: false }).collection['List'];
                            const matchingMemoryLocations = memoryLocations.filter(ml => ml.name === (cueText));
                            
                            /**
                             * @param {object} obj
                             * @param {string} obj.name
                             */
                            function createMemoryLocationsMarker({ name }) {
                                sf.ui.proTools.appActivateMainWindow();
                            
                                const dlg = sf.ui.proTools.newMemoryLocationDialog;
                            
                                if (!dlg.exists) {
                                    sf.keyboard.press({ keys: "numpad enter" });
                                }
                            
                                dlg.elementWaitFor();
                            
                                dlg.radioButtons.whoseTitle.is("Marker").first.elementClick();
                            
                                dlg.textFields.allItems[1].elementSetTextFieldWithAreaValue({
                                    value: name,
                                });
                            
                                dlg.buttons.whoseTitle.is("OK").first.elementClick();
                            
                                dlg.elementWaitFor({ waitType: "Disappear", });
                            }
                            
                            function pasteAtMemoryLocation(memoryLocation) {
                                sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: memoryLocation.number, });
                            
                                sf.ui.proTools.menuClick({
                                    menuPath: ["Edit", "Paste"],
                                });
                            }
                            
                            function clearMemoryLocations() {
                                sf.ui.proTools.memoryLocationsShowWindow();
                            
                                memoryLocations.forEach(memLoc => {
                                    sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: memLoc.number, });
                            
                                    sf.ui.proTools.memoryLocationsWindow.popupButtons.first.popupMenuSelect({
                                        menuPath: ['Clear*'],
                                        useWildcards: true,
                                    });
                                });
                            };
                            
                            /**
                             * @param {object} obj
                             * @param {string} obj.locationName
                             * @param {string} obj.direction
                             */
                            function goToMatchedMemoryLocation({ locationName, direction }) {
                                let currentTimecode = sf.ui.proTools.getCurrentTimecode().stringValue;
                            
                                //If main counter is bars beats, remove last three digits, since memory locations list ignores them
                                currentTimecode.match(/\|/) ? currentTimecode = currentTimecode.slice(0, -3) : null;
                            
                                let cleanMainCounter = Number(currentTimecode.replace(/[^0-9]/g, '').trim());
                            
                                let targetMemoryLocation;
                            
                                if (direction === "next") {
                                    targetMemoryLocation = matchingMemoryLocations.filter(x =>
                                        Number(x.mainCounterValue.replace(/[^0-9]/g, '').trim()) > cleanMainCounter
                                    )[0].number;
                                } else if (direction === "previous") {
                                    targetMemoryLocation = matchingMemoryLocations.filter(x =>
                                        Number(x.mainCounterValue.replace(/[^0-9]/g, '').trim()) < cleanMainCounter
                                    ).reverse()[0].number;
                                }
                            
                                try {
                                    sf.ui.proTools.memoryLocationsGoto({ memoryLocationNumber: targetMemoryLocation, });
                                } catch (err) {
                                    log(`There are no "${direction}"" location markers containing:\n"${locationName}"`);
                                }
                            }
                            
                            /** Counts the amount of times this function is called during a fixed amount of time 
                             * and passes that value to a callback from which different actions can take place.
                             * @param {object} args
                             * @param {string} args.name - Name for this instance.
                             * @param {number} args.waitTime - Amount of time to wait in milliseconds. 
                             * @param {function} args.action - Callback to execute when the waitTime is over. Counter value will be passed as an argument.
                             */
                            function actionCounter({ name, waitTime, action }) {
                                let actionCounter;
                            
                                if (!globalState.actionCounter) globalState.actionCounter = {};
                                actionCounter = globalState.actionCounter;
                            
                                if (!actionCounter[name]) {
                                    actionCounter[name] = {
                                        dateMs: (new Date).valueOf(),
                                        i: 1
                                    };
                                } else {
                                    actionCounter[name].i++
                                    return;
                                }
                            
                                sf.engine.runInBackground(() => {
                                    sf.wait({ intervalMs: waitTime, executionMode: "Background" });
                                    try {
                                        action(actionCounter[name].i);
                                    } finally {
                                        delete globalState.actionCounter;
                                    }
                                });
                            }
                            
                            function main() {
                                if (!isMemLocWinOpened) {
                                    sf.ui.proTools.menuClick({
                                        menuPath: ['Window', 'Memory Locations'],
                                        targetValue: "Enable",
                                    });
                                };
                            
                                const modifierState = event.keyboardState.asString;
                            
                                const isCreateMode = globalState.toggleCreateOrGoTo === undefined || globalState.toggleCreateOrGoTo === true;
                                const isGoto = globalState.toggleCreateOrGoTo !== undefined && globalState.toggleCreateOrGoTo === false;
                            
                                switch (true) {
                            
                                    // Create mode with Command modifier
                                    case modifierState === "cmd":
                                        memoryLocations.forEach(pasteAtMemoryLocation);
                                        break
                            
                                    // Create mode with Shift modifier
                                    case modifierState === "shift":
                                        clearMemoryLocations();
                                        break
                            
                                    // Create mode with no modifiers
                                    case isCreateMode && modifierState === "":
                                        createMemoryLocationsMarker({ name: cueText });
                                        break
                            
                                    // GotTo mode with no modifiers
                                    case isGoto && modifierState === "":
                                        actionCounter({
                                            name: 'memoryLocaitonFowardorBackwards',
                                            waitTime: 500,
                                            action: i => {
                                                switch (i) {
                            
                                                    //Single press within Action Counter wait time
                                                    case 1:
                                                        goToMatchedMemoryLocation({
                                                            locationName: cueText,
                                                            direction: "next"
                                                        });
                                                        break;
                            
                                                    //Single press within Action Counter wait time
                                                    case 2:
                                                        goToMatchedMemoryLocation({
                                                            locationName: cueText,
                                                            direction: "previous"
                                                        });
                                                        break;
                                                }
                                            }
                                        });
                                        break
                                };
                            
                                //Restore initial state of Memory Location window
                                if (!isMemLocWinOpened) {
                                    sf.ui.proTools.menuClick({
                                        menuPath: ['Window', 'Memory Locations'],
                                        targetValue: "Disable",
                                    });
                                };
                            }
                            
                            main();
                            

                            Let me know how it runs for you.

                            Rock on!

                            1. Works like Gangbusters!!! Package has been updated. You've done so much work man thank you.

                              Now that you've done so much work just take the package away from me and turn it into a dynamic deck like Christians ProTools Spotting :D

                              But really, thanks again, @Peter_Gallacher I hope this speeds it back up?

                              Lets us know.

                              1. Peter Gallacher @Peter_Gallacher
                                  2022-05-06 16:25:24.562Z

                                  Hi @Owen_Granich_Young that's great news. Will you update the app so I can get the updated version? Or do I just add the code in manually? If so, just remind me of where it goes again.

                                  1. I've updated the package 👍🏼

                                    1. Kitch Membery @Kitch2022-05-06 16:50:18.414Z

                                      Fingers crossed it speeds things up!!!

                                      1. Peter Gallacher @Peter_Gallacher
                                          2022-05-06 17:14:33.983Z

                                          Hi @Kitch @Owen_Granich_Young It's like Superman coming back from the dead in Justice League. Well done Kitch, awesome work once again. have a fab weekend folks. https://www.youtube.com/watch?v=gHKe5pT9sqk

                                          1. Kitch Membery @Kitch2022-05-06 17:16:25.326Z

                                            Yesssss!!!! Woooo!!!

                                            Glad it worked:-)

                                            1. Niiiiiiiice

                        2. In reply toPeter_Gallacher:
                          Peter Gallacher @Peter_Gallacher
                            2023-07-27 10:49:08.173Z

                            Hi @Owen_Granich_Young @Kitch since I updated to the latest PT in June, my memory locations don't seem to add the text I used as presets. I've updated the app, but still no luck. Is there something I need to change/update.

                            Thanks in advance
                            Peter

                            1. Hey Peter, 2023.6 introduced new memory locations and it broke everything... sorry I haven't updated or dug into updating the script for it.

                              1. Peter Gallacher @Peter_Gallacher
                                  2023-07-27 16:09:09.641Z

                                  Bless ya, no problem. When you get time to open the hood, then loop me in. Happy to help where I can as well.

                                  1. Hors Piste @Hors_Piste
                                      2023-11-22 10:56:48.108Z

                                      I would LOVE to see this package updates... would save so much time here ! Just hit me if I can help in some ways !

                                      1. Now with SDK, better faster stronger.... I know a really long wait.