No internet connection
  1. Home
  2. How to

How to get a different notification after the script fails?

By Davide Favargiotti @dieffe
    2018-09-10 22:38:08.781Z

    OK, bear with me, this one will be a long one.

    I changed one of my script to move region to tracks with a specific name. here's the script:

    //This script will move the content in the selected track to track named "Boom Strip 1", "Boom Strip 2", etc.
    
    // this script need the script "Read preferences from Pro Tools Session Folder"
    // and also need destination tracks named with a specific structure
    // you can use the script "Create Tracks from variables" to create those specific tracks, from the preferences
    // set with the script "Read preferences from Pro Tools Session Folder"
    
    
    
    sf.ui.proTools.mainWindow.invalidate();
    var num = sf.ui.proTools.selectedTrackCount    	//get the number of selected track
    var track = 'Boom Strip'						//Base name of the tracks to move the clips
    var names = []
    var sel = sf.ui.proTools.selectionGet();
    var origTrackName = sf.ui.proTools.selectedTrackNames;
    var paste = 0
    
    if (num > globalState.boomStripTracks) {		//check if you have enough destination tracks
    	log('Please select less tracks');
    	throw 0;
    }
    
    //populate the destination tracks array with the name of the tracks
    for (var y = 1; y < (globalState.boomStripTracks + 1); y++) {
    	names[(y - 1)] = [track + globalState.nameSeparation + y.toString()];
    }
    
    // cycle: it will the selected region in each track, and for each track it will cycle to find an empy spot 
    // on the destination tracks to copy the material. After copying the material il will delete the selection in the source track
    for (var i = 0; i < num; i++) {
    	sf.ui.proTools.trackSelectByName({ names: [origTrackName[i]] });
    	sf.ui.proTools.selectionSet({ selectionLength: sel.selectionLength });
    	// sf.wait({ intervalMs: 100 });
    	sf.ui.proTools.menuClick({ menuPath: ['Edit', 'Copy'] });
    	paste = 1
    	var j = 0
    	// sf.wait({ intervalMs: 100 });
    
    	while (paste != 0) {
    		sf.ui.proTools.trackSelectByName({
    			names: names[j],
    			deselectOthers: true,
    		});
    		sf.ui.proTools.selectionSet({ selectionLength: sel.selectionLength });
    		sf.keyboard.press({
    			keys: 'left'
    		});
    		if (sf.ui.proTools.getMenuItem('Edit', 'Fades').isEnabled) {
    			j++;
    		}
    		else {
    			sf.ui.proTools.menuClick({ menuPath: ['Edit', 'Paste'] });
    			sf.ui.proTools.trackSelectByName({ names: [origTrackName[i]] });
    			sf.ui.proTools.selectionSet({ selectionLength: sel.selectionLength });
    			sf.ui.proTools.menuClick({ menuPath: ['Edit', 'Clear'] });
    			paste = 0
    		};
    	};
    
    
    }
    
    
    
    

    the issue that I have is that if the script doesn't find an empty track to copy the material, SF will give an error.
    I'm fine with that, but I would like to have a better log instead, more user friendly.
    How can I do that?

    btw, before running the script above you need to run the script below (to populate the global variables, used in the script above)

    // This script reads a tab delimited file and set globalstate variables to the values written in the file.
    // The text file is jsut a TAB delimited file (so use a TAB after the name), but I decided to use a different extension (.dfp)
    // to be sure that SF read the correct file. Obviously you can change that in the extCheck variable.
    // This script looks in the text file for the names that match the switch/case statement, and assign those values to globalstate variables.
    // My text file that matches this script look like this:
    // 
    // boomstrip    1
    // radiostrip   1
    // fill 1
    // toadr    1
    // untreated    1
    // orig 1 
    // edit 1
    // option   1
    // separation   _
    // 
    // This file will set all the variables below (that are set to default state at the beginning of the script) to 1.
    
    var sessionPath = sf.ui.proTools.mainWindow.sessionPath;
    var sessionDir = sessionPath.split('/').slice(0, -1).join('/');
    var fullPath = sessionDir + '/settings.dfp';
    
    var i
    var x
    
    // defualt values
    globalState.boomStripTracks = 4
    globalState.radioStripTracks = 6
    globalState.fillTracks = 2
    globalState.toBeAdrTracks = 6
    globalState.untreatedTracks = 8
    globalState.origTracks = 4
    globalState.editTracks = 4
    globalState.optionTracks = 2
    globalState.nameSeparation = ' '
    
    if (!sf.file.exists({ path: fullPath }).exists) {
        log('Error', 'Could not find a settings.dfp in the current session\'s folder');
        sf.wait({intervalMs: 1500})
        log('Loading default values', '');
        throw 0; //exit script now
    }
    
    var nameList = sf.file.readLines({
        path: fullPath
    }).lines;
    
    for (i = 0; i < nameList.length; i++) {
        var thisLine = nameList[i];
        var lineArray = thisLine.split('\t');
        switch (lineArray[0]) {
            case 'boomstrip':
                globalState.boomStripTracks = parseInt(lineArray[1]);
                break;
            case 'radiostrip':
                globalState.radioStripTracks = parseInt(lineArray[1]);
                break;
            case 'fill':
                globalState.fillTracks = parseInt(lineArray[1]);
                break;
            case 'toadr':
                globalState.toBeAdrTracks = parseInt(lineArray[1]);
                break;
            case 'untreated':
                globalState.untreatedTracks = parseInt(lineArray[1]);
                break;
            case 'edit':
                globalState.editTracks = parseInt(lineArray[1]);
                break;
            case 'separation':
                globalState.nameSeparation = lineArray[1];
                break;
            case 'option':
                globalState.optionTracks = parseInt(lineArray[1]);
                break;
            case 'orig':
                globalState.origTracks = parseInt(lineArray[1]);
        }
    
    }
    
    log('Boom Strips tracks:', globalState.boomStripTracks.toString());
    log('Radio Strips tracks:', globalState.radioStripTracks.toString());
    log('Fill tracks:', globalState.fillTracks.toString());
    log('To Be ADR tracks:', globalState.toBeAdrTracks.toString());
    log('Untreated tracks:', globalState.untreatedTracks.toString());
    log('Original tracks:', globalState.origTracks.toString());
    log('Edit tracks:', globalState.editTracks.toString());
    log('Option tracks:', globalState.optionTracks.toString());
    

    And also you can use the following script to create the needed tracks, after you run the script above to populate the global variables.

    function createTracks(numtracks, baseName, active, colBright, colNum) {
    	for (var x = 0; x < numtracks; x++) {
    
    		//Open New Track dialog
    		sf.ui.proTools.menuClick({
    			menuPath: ['Track', 'New...']
    		});
    
    		//Wait for the New Track dialog, and click create in it
    		var ntDlg = sf.ui.proTools.dialogWaitForManual({ dialogTitle: 'New Tracks' }).dialog;
    		ntDlg.getFirstWithTitle("Create").elementClick();
    
    		//Wait for the dialog to close again
    		sf.ui.proTools.waitForNoModals();
    
    		//Refresh SF's cache of tracks since we just added one
    		sf.ui.proTools.mainWindow.invalidate();
    
    		//Rename the selected track
    		sf.ui.proTools.selectedTrack.trackRename({
    			renameFunction: function (name) {
    				return baseName + globalState.nameSeparation + (x + 1).toString();
    			}
    		});
    		// Make the track inactive
    		if (active == false) {
    			sf.ui.proTools.menuClick({
    				menuPath: ['Track', 'Make Inactive']
    			});
    		}
    		sf.ui.proTools.colorsSelect({
    			colorBrightness: colBright,
    			colorNumber: colNum,
    			colorTarget: 'Tracks'
    		})
    	}
    }
    
    // createTracks(globalState.fillTracks, 'FILL', false, 'Light', 20);
    // createTracks(globalState.editTracks, 'EDIT', true, 'Light', 12);
    createTracks(globalState.boomStripTracks, 'Boom Strip', false, 'Dark', 23);
    // createTracks(globalState.radioStripTracks, 'Radio Strip', false, 'Dark', 23);
    //  createTracks(globalState.toBeAdrTracks, 'ToBeADR', false, 'Dark', 23 );
    //createTracks(globalState.untreatedTracks, 'Untreated', false, 'Dark', 23);
    // createTracks(globalState.origTracks, 'ORIG', false, 'Dark', 23);
    //createTracks(globalState.optionTracks, 'Optional', false, 'Dark', 23);
    

    Sorry, you have to un-comment the last block to create more block of tracks: I have yet to find a better way to do that.

    Solved in post #11, click to view
    • 14 replies
    1. D
      Davide Favargiotti @dieffe
        2018-09-10 22:42:29.437Z

        And yes, I'm going to make a package of these scripts, but first I need to be sure they work :)

        1. Awesome!

          1. DDavide Favargiotti @dieffe
              2018-09-11 07:15:58.079Z

              Ok so, what’s the best way to “package” these script?the way I wrote them, they all depend from these global state variables.
              I like it because I can have script that interact with each other... and i’m thinking to expand even more this thing.
              But it can be confusing for another user.
              At the same time don’t want to maintain two different version of the scripts.
              So what’s the question: what’s the best way to explain how the user should and could customize these scripts?
              Because if somebody use them without e.g launching the main script before, they just don’t work... :)

              1. @dieffe on my way to the airport now so will just give you a short reply now.
                In essence this is what the Package description is for, and the INFO tab on each command. So you can both write a general introduction text to the package explaining how it works, and on each script you can just write a short description of what that script does.

                1. DDavide Favargiotti @dieffe
                    2018-09-11 07:40:38.573Z

                    Yep, obviously that make sense.
                    I was thinking if there is a way to mark up or highlight dependencies in a more structured way than just writing some text :)

                    1. Yea. I haven't had time to really go through the scripts yet, travel day, but I would be curious to hear ideas about how we could make some more structured info available. I think we will encounter many areas of possible extensions of the package functions once we have more packages created so it's great to gather ideas before we make any architectural changes.

                      1. DDavide Favargiotti @dieffe
                          2018-09-14 10:17:48.464Z

                          I was thinking to start with something simple like colouring the scripts with some colour scheme (fixed) that clearly show the dependencies.
                          But also, (maybe?) if tags are implemented it should be easy to add a specific tag that create a relation between multiple scripts.
                          Or maybe it's possible to implement this automatically e.g. when global variable are used, SF could determine where the same var is used somewhere else in the scripts (in that package or elsewhere) and show that.

                          Maybe the code should be highlighted (optionally) to show which variables or other stuff is connected to "stuff" outside that script?

                          1. @dieffe let's take this discussion in a separate thread so it doesn't get forgotten in here. I think I would have to look more thoroughly in the scripts you are referring to to understand why a user needs to know which scripts are technically related.
                            From the top of my head, auto analysis of globalState variables sounds like a very complicated solution to something that could possibly be solved more easily. Or maybe I just need to understand more about what problem it's trying to solve. If it's for the end user and/or for the developer etc. But again, let's take that discussion in a separate idea thread :)

              2. D
                In reply todieffe:
                Davide Favargiotti @dieffe
                  2018-09-12 09:09:06.395Z

                  Another problem I have with this script is that if there some automation that will be lost when copying to the new track (and the warning menu appears) the whole thing falls apart.
                  I couldn't find a way to tell SF (it's in there, I'm sure) that if a menu that requires user interaction appears, it has to pause the script until the menu disappears.
                  I tried

                  sf.ui.proTools.waitForNoModals({pollingInterval:20})
                  
                  

                  and

                  sf.ui.proTools.dialogWaitForNoDialogs()
                  

                  But neither seems to work (the script sort of skip a loop and doesn't copy one track)

                  1. The wait for no modals will wait until there are no modals present. So if the action is run before any modals are present (let's say it takes Pro Tools a very tiny amount of time to display the popup) then you won't get the behavior you're after.
                    Basically you'll need to define how long you'd like to wait for PT to display the possible popup before then invoking waitForNoModals.
                    So: invoke menu item, wait for maybe 100, 200 ms? then invoke waitForNoModals. The more stable solution is to be able to predict if there's gonna be a popup or not - if you now it for certain, then that wait should be structured differently.

                  2. In reply todieffe:

                    @dieffe back at the studio now. Ok, so generally SF's Javascript in in line with how Javascript normally treats errors. That is whenever an error occurs in a SF action, it throws an exception. If that exception is not caught by the script, it will bubble up to the SF code running your command and it will then report the exception as an error.
                    You can however catch those exceptions if you'd like to, and decide what to do in your script.
                    Catching exceptions in Javascript is done in try/catch blocks (you should google that to get better/longer explanations but here's a short summary).

                    Example:

                    try {
                        //If there's anything inside this try block that throws an exception (let's say an action that fails) the try-block will immediately stop executing and the script will jump to the corresponding catch-block.
                    }
                    catch(err) {
                        //This code will run if there was an error inside the try block.
                        log('The script failed completely. Abort abort');
                    
                        //Here you can then choose what to do. If you still want the script to terminate you can do:
                        throw 0;
                    }
                    
                    1. In reply todieffe:

                      All the options for handling errors in SF actions are:

                      1: try/catch blocks as described above.

                      try {
                          sf.ui.proTools.trackSelectByName({
                              names: ['Audio 1'],
                              deselectOthers: true,
                          });
                      } catch(err) {
                          log('Could not select Audio 1 track');
                          throw 0; //abort the script
                      }
                      

                       
                      2: use error callbacks for the action. when you specify a error callback function, the action won't throw an exception it will instead call the error function

                      sf.ui.proTools.trackSelectByName({
                          names: ['Audio 1'],
                          deselectOthers: true,
                      }, function(err) {
                          log('Could not select Audio 1 track');
                          throw 0; //abort the script
                      });
                      

                       
                      3: Directly specify the error message to be displayed if the action fails. IF the action fails, it will display the error message and terminate the script.

                      sf.ui.proTools.trackSelectByName({
                          names: ['Audio 1'],
                          deselectOthers: true,
                      }, 'Could not select Audio 1 track');
                      
                      Reply1 LikeSolution
                      1. DDavide Favargiotti @dieffe
                          2018-09-14 10:09:49.411Z

                          Very cool.
                          actually I noticed that the option 3 is noted in the help documentation in the script editor, I just didn't understand what the help was telling me.

                          I used the try/catch and works very well, easy to implement and works great, it seems.

                          Also I added a sf.wait() before the sf.ui.proTools.waitForNoModals() and now it works, hurrah!

                          Thanks you! I'll try to create a simple package with some of these scripts during the weekend, if I can take a break from editing