No internet connection
  1. Home
  2. How to
  3. Ableton Live

Script To Arm and Select a given track is buggy / inconsistent

By wolaud @wolaud
    2025-06-07 02:57:58.791Z

    Title

    Script To Arm and Select a given track is buggy / inconsistent

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

    This is a script worked on previously on the forum to select and arm a named track.

    Are you seeing an error?

    AbletonLiveSetObjectValueAction: Object reference not set to an instance of an object.

    What happens when you run this script?

    It spits out the given error ("AbletonLiveSetObjectValueAction: Object reference not set to an instance of an object.")

    I am using the same script across a number of other named tracks and it seems to work perfectly for 90% of them. This and 1 or 2 others exhibit odd behavior.

    How were you running this script?

    I clicked the "Run Script" or "Run Macro" button in SoundFlow

    How important is this issue to you?

    5

    Details

    {
        "inputExpected": "This is a script worked on previously on the forum to select and arm a named track. ",
        "inputIsError": true,
        "inputError": "AbletonLiveSetObjectValueAction: Object reference not set to an instance of an object.",
        "inputWhatHappens": "It spits out the given error (\"AbletonLiveSetObjectValueAction: Object reference not set to an instance of an object.\")\n\nI am using the same script across a number of other named tracks and it seems to work perfectly for 90% of them. This and 1 or 2 others exhibit odd behavior.",
        "inputHowRun": {
            "key": "-MpfwYA4I6GGlXgvp5j1",
            "title": "I clicked the \"Run Script\" or \"Run Macro\" button in SoundFlow"
        },
        "inputImportance": 5,
        "inputTitle": "Script To Arm and Select a given track is buggy / inconsistent"
    }

    Source

    // Bail out if Ableton is not running
    if (!sf.ui.abletonLive.isRunning) throw `Ableton Live is not running`;
    
    // Track Names
    let tracks = ['VST 1'];
    
    // Toggle arm state for each track
    tracks.forEach((trackName) => {
        let track = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
    
        if (track && track.arm) {
            let currentArmState = track.arm.value;
            track.arm.setValue({ value: !currentArmState });
        }
        // Track to Select
    let trackToSelect = 'VST 1';
    
    // Filter for Track to Select
    let track = sf.app.abletonLive.song.visibleTracks.find(t => t.name.value === trackToSelect);
    
    // Set Selected Track
    sf.app.abletonLive.song.view.selectedTrack.setValue({ value: track });
    });
    

    Links

    User UID: YwZWhQkNk9YC9IGtGtlgw4vfPEt1

    Feedback Key: sffeedback:YwZWhQkNk9YC9IGtGtlgw4vfPEt1:-OS7JhYMFM9FekTOOxdj

    Feedback ZIP: n4+IiHQQ6aoSIM7WU9Chd8u1PJcvhKUL7/FgTBFS+VMFwoICbV+8IeI40RhsCGnS4ttTB4MDAFy9ScmqI1KC4JyjgF+JvCAJkqGtmnrLARtHKnYWcc6uEHZLd/+CpuDpjzCgSkZdXlJ76WVBXsHurAsQdSWX5fR6YE2LXaPMezOewXLgTxmvONSOdrvdAMd7GDvD56x3vSoPGHx5BuOGMZCaiNQU+lqSJJXX3xbhfUAxFJQwMzdRAQGHyNTXQ2GKKvKb1FqCtLRUehgTXAzYs+wbhHxgQf1cNaS8Gv0FS5JlbEv0+eHKwm0ZAOsT3EUd3bK2EWGyl5PaIjjuvL4FCKsiTZchVub0/Hv3nVxcbB4=

    Solved in post #8, click to view
    • 27 replies

    There are 27 replies. Estimated reading time: 52 minutes

    1. W
      wolaud @wolaud
        2025-06-07 02:59:29.195Z

        An added function I would love to add to this script (once it's working) would be to have it also search the clip slots and select the first empty one. Currently it defaults to whatever scene happens to be selected.

        But it would be great to get it working more consistently for now of course!

        1. Chad Wahlbrink @Chad2025-06-08 02:01:55.466Z

          Thanks, @wolaud! I am planning to look into this more on Monday and also follow up with any other thoughts from the previous post:

          If you're up for it, it could be nice to hop on a quick Zoom call and discuss how to approach these scenarios with Ableton more generally (working with track selection, checking clip slots for empty slots, etc.). We can do a lot by bouncing between UI automation and the Ableton Control Surface API with SoundFlow. In this instance, I think we'll need to use some UI automation to record to the next empty slot, but it should be possible!

          If a Zoom call interests you, contact me at support@soundflow.org, and I'll set it up.

          Regardless, I'll have more for you early this coming week!

          1. Wwolaud @wolaud
              2025-06-10 19:08:11.631Z

              Thanks Chad! I'll keep an eye out for your updates and reach out about getting a Zoom scheduled.

              1. Chad Wahlbrink @Chad2025-06-11 19:30:02.358Z

                Hi @wolaud,

                I tried your script and couldn't reproduce inconsistencies on my machine. I'd be curious if the session has multiple tracks named "VST 1" - that could potentially lead to issues with finding the correct track.

                Here's a slightly refactored version of the script you shared above:

                // Bail out if Ableton is not running
                if (!sf.ui.abletonLive.isRunning) throw `Ableton Live is not running`;
                
                // Track Names
                let tracks = ['VST 1'];
                
                // Toggle arm state for each track
                tracks.forEach((trackName) => {
                    // Find the Track
                    let track = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
                
                    // Toggle Arm The Track
                    if (track && track.arm) {
                        let currentArmState = track.arm.value;
                        track.arm.setValue({ value: !currentArmState });
                    }
                
                    // Set Selected Track
                    sf.app.abletonLive.song.view.selectedTrack.setValue({ value: track });
                });
                

                There were some syntax errors in the version you shared, because let track was repeated twice in the same scope. It's possible that this could have caused issues in rare instances.

                As for arming the next free slot, I think this should do it for you. Although, I'm just always setting the arm state to true instead of toggling, if you do want it toggle and then record to the next empty slot, just let me know:

                // Bail out if Ableton is not running
                if (!sf.ui.abletonLive.isRunning) throw `Ableton Live is not running`;
                
                // Track Names
                let tracks = ['VST 1'];
                
                // Toggle arm state for each track
                tracks.forEach((trackName) => {
                    // Find the Track
                    let track = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
                
                    // Set Selected Track
                    sf.app.abletonLive.song.view.selectedTrack.setValue({ value: track });
                
                    // Toggle Arm The Track
                    if (track && track.arm) {
                        track.arm.setValue({ value: true });
                        // Record to next free slot
                        track.clipSlots.invalidate().find(clipslot => clipslot.hasClip.boolValue === false).fire()
                
                    }
                });
                
                1. Chad Wahlbrink @Chad2025-06-11 19:33:02.692Z

                  Also, here's a quick video of me playing with your script if it's helpful.

                  I mention invalidation, which could be why you see some inconsistency with this script and potentially the one used to read the input level for arming/disarming. Here's a forum post on invalidation as well:

          2. In reply towolaud:
            Chad Wahlbrink @Chad2025-06-13 16:42:28.108Z

            Hi @wolaud,

            I made some progress on the script to select the next free clip slot.

            To select the next free clip slot on a named track like "VST 1" you can use this script:

            // Bail out if Ableton is not running
            if (!sf.ui.abletonLive.isRunning) throw `Ableton Live is not running`;
            
            // Track Name
            let trackName = 'VST 1';
            
            // Find the Track
            let trackToArm = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
            
            // Toggle Arm The Track
            trackToArm.arm.setValue({ value: true });
            
            // Find the next Empty Clip Slot on the Track
            let uiClipSlots = sf.ui.abletonLive.mainWindow.session.slots.groups.filter(group => group.title.value.includes(`in track ${trackName},`));
            let firstEmptyClipSlot = uiClipSlots.findIndex(group => group.title.value.startsWith(`in track ${trackName}`));
            
            // Highlight Clip Slot
            sf.app.abletonLive.song.view.highlightedClipSlot.setValue({ value: trackToArm.clipSlots[firstEmptyClipSlot] });
            

            To do this for the selected Track, it would be this. Note that using sf.app.abletonLive.song.view.selectedTrack.invalidate(); in a session as big as the one you are working from may be a bit slow, but it is accurate - we may be able to work out a quicker method.

            // Bail out if Ableton is not running
            if (!sf.ui.abletonLive.isRunning) throw `Ableton Live is not running`;
            
            // Arm the Selected Track 
            let trackToArm = sf.app.abletonLive.song.view.selectedTrack.invalidate();
            
            let trackName = trackToArm.name.stringValue;
            
            // Toggle Arm The Track
            trackToArm.arm.setValue({ value: true });
            
            // Find the next Empty Clip Slot on the Track
            let uiClipSlots = sf.ui.abletonLive.mainWindow.session.slots.groups.filter(group => group.title.value.includes(`in track ${trackName},`));
            let firstEmptyClipSlot = uiClipSlots.findIndex(group => group.title.value.startsWith(`in track ${trackName}`));
            
            // Highlight Clip Slot
            sf.app.abletonLive.song.view.highlightedClipSlot.setValue({ value: trackToArm.clipSlots[firstEmptyClipSlot] });
            

            Then to record to the selected clip slot, you can use a script like this:

            // Bail out if Ableton is not running
            if (!sf.ui.abletonLive.isRunning) throw `Ableton Live is not running`;
            
            sf.app.abletonLive.song.view.highlightedClipSlot.fire();
            
            1. Wwolaud @wolaud
                2025-06-13 23:10:35.383Z

                Thanks Chad. Unfortunately the first one (select next free clip slot) does not seem to be working for me and gives the following error:

                13.06.2025 16:03:35.91 [Backend]: Logging unknown error in action (02) AbletonLiveSetObjectValueAction: Object reference not set to an instance of an object.

                When triggered it will arm the track if it isn't armed already and throw that error.

                The ideal version of this one for me would arm the track if it's not already armed and select the empty clip slot, and disarm the track if it's already armed.

                1. Chad Wahlbrink @Chad2025-06-14 01:01:38.178Z

                  That should be doable! I’ll do some sleuthing on the error you saw when I am back at my computer again.

                  It was working for me this morning on your template live set, it would need to be in session view for it to work currently.

                  1. Wwolaud @wolaud
                      2025-06-14 01:33:38.454Z

                      Okay so after restarting my machine it does in fact appear to work! Not sure why that would be but I'll take it as a win!

                      1. Wwolaud @wolaud
                          2025-06-14 01:40:17.937Z

                          So the next thing that would be great would be to make it toggle the state of the track arm instead of always set it to on. If that is possible.

                          Another thing I'm wondering is if this could be modified to select a range or group of multiple tracks?

                          1. Wwolaud @wolaud
                              2025-06-18 15:36:06.589Z

                              Hey Chad, wanted to follow up on this as I'm hoping to modify a script above and having a bit of trouble. Building off of this:

                              // Bail out if Ableton is not running
                              if (!sf.ui.abletonLive.isRunning) throw `Ableton Live is not running`;
                              
                              // Track Name
                              let trackName = 'VST 1';
                              
                              // Find the Track
                              let trackToArm = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
                              
                              // Toggle Arm The Track
                              trackToArm.arm.setValue({ value: true });
                              
                              // Find the next Empty Clip Slot on the Track
                              let uiClipSlots = sf.ui.abletonLive.mainWindow.session.slots.groups.filter(group => group.title.value.includes(`in track ${trackName},`));
                              let firstEmptyClipSlot = uiClipSlots.findIndex(group => group.title.value.startsWith(`in track ${trackName}`));
                              
                              // Highlight Clip Slot
                              sf.app.abletonLive.song.view.highlightedClipSlot.setValue({ value: trackToArm.clipSlots[firstEmptyClipSlot] });
                              

                              This works perfectly - but I can't figure out a way to make it Arm multiple tracks (say VST1 and VST2) but then highlight only ONE clip slot — let's say VST1.

                              I was also wondering if it's possible to trigger a cycling list of macros with each button press? Say for example:

                              Button Press 1: Arm (or toggle track arm) for VST1 and VST2 and selects the first empty clip on VST1
                              Button Press 2: Switches selection to the first empty clip slot on VST2
                              Button Press 3: Disarm (or toggle) track arm VST1 and VST2.

                              1. Chad Wahlbrink @Chad2025-06-19 16:40:23.628Z

                                Okay, for question one:

                                This works perfectly - but I can't figure out a way to make it Arm multiple tracks (say VST1 and VST2) but then highlight only ONE clip slot — let's say VST1.

                                Here's a refactored version of the script:

                                // Bail out if Ableton is not running
                                if (!sf.ui.abletonLive.isRunning) throw `Ableton Live is not running`;
                                
                                /**
                                 * Arm Next Free Clip Slot
                                 * @param {Object} params - params for function
                                 * @param {string} params.trackName - Track to Target
                                 */
                                function armNextFreeClipSlot({ trackName }) {
                                    // Find the Track
                                    let trackToTarget = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
                                
                                    // Find the next Empty Clip Slot on the Track
                                    let uiClipSlots = sf.ui.abletonLive.mainWindow.session.slots.groups.filter(group => group.title.value.includes(`in track ${trackName},`));
                                    let firstEmptyClipSlot = uiClipSlots.findIndex(group => group.title.value.startsWith(`in track ${trackName}`));
                                
                                    // Highlight Clip Slot
                                    sf.app.abletonLive.song.view.highlightedClipSlot.setValue({ value: trackToTarget.clipSlots[firstEmptyClipSlot] })
                                }
                                
                                /**
                                 * Arm the Track for Recording
                                 * @param {Object} params - params for function
                                 * @param {string} params.trackName - Track to Target
                                 */
                                function armTrackForRecording({ trackName }) {
                                    // Find the Track
                                    let track = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
                                
                                    // Arm The Track
                                    track.arm.setValue({ value: true });
                                }
                                
                                // Track Names
                                let tracks = ['VST 1', 'VST 2'];
                                
                                // Toggle arm state for each track
                                tracks.forEach((trackName) => {
                                    armTrackForRecording({ trackName })
                                });
                                
                                armNextFreeClipSlot({ trackName: 'VST 1' });
                                
                                1. Chad Wahlbrink @Chad2025-06-19 16:43:09.157Z

                                  @wolaud, Let me explore some possible solutions for the cycling command.

                                  If firing from a stream deck, would it be sufficient to allow modifiers to trigger the different actions? Or is it best if it's kind of all automatic?

                                  For example, you could easily do something like:

                                  ...
                                  If option is held on the keyboard and stream deck button is pushed, then switch to next available slot on the next track of the group (VST 2 in the example).

                                  ...
                                  If control is held then disarm all tracks in group.

                                  1. Wwolaud @wolaud
                                      2025-06-19 16:52:01.410Z

                                      Thanks Chad.

                                      I think automatic is best if it's possible, but all good if modifiers are the only way to pull it off.

                                  2. In reply towolaud:
                                    Chad Wahlbrink @Chad2025-06-19 17:28:34.541Z

                                    @wolaud, here's a script that will cycle as you suggested. When you make script changes, I recommend restarting SoundFlow to clear the global state.

                                    You may also want to set individual global state variables for each "group of tracks" - to do this, change each instance of globalState['AbletonTracksToArm'] in the script.

                                    For instance, you could make it more specific to the VST tracks by using this in its place:
                                    globalState['AbletonTracksToArm-VST']

                                    // Bail out if Ableton is not running
                                    if (!sf.ui.abletonLive.isRunning) throw `Ableton Live is not running`;
                                    
                                    /**
                                     * Arm Next Free Clip Slot
                                     * @param {Object} params - params for function
                                     * @param {string} params.trackName - Track to Target
                                     */
                                    function armNextFreeClipSlot({ trackName }) {
                                        // Find the Track
                                        let trackToTarget = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
                                    
                                        // Find the next Empty Clip Slot on the Track
                                        let uiClipSlots = sf.ui.abletonLive.mainWindow.session.slots.groups.filter(group => group.title.value.includes(`in track ${trackName},`));
                                        let firstEmptyClipSlot = uiClipSlots.findIndex(group => group.title.value.startsWith(`in track ${trackName}`));
                                    
                                        // Highlight Clip Slot
                                        sf.app.abletonLive.song.view.highlightedClipSlot.setValue({ value: trackToTarget.clipSlots[firstEmptyClipSlot] })
                                    }
                                    
                                    /**
                                     * Arm the Track for Recording
                                     * @param {Object} params - params for function
                                     * @param {Object} params.track - Track to Target
                                     * @param {'Enable'|'Disable'|'Toggle'} [params.targetValue='Toggle'] - Target Value
                                     */
                                    function armForRecording({ track, targetValue = 'Toggle' }) {
                                        if (targetValue === 'Toggle') {
                                            let currentArmState = track.arm.value;
                                            track.arm.setValue({ value: !currentArmState });
                                        } else if (targetValue === 'Enable') {
                                            // Arm The Track
                                            track.arm.setValue({ value: true });
                                        } else if (targetValue === 'Disable') {
                                            // Disarm The Track
                                            track.arm.setValue({ value: false });
                                        }
                                    }
                                    
                                    // Track Names
                                    let tracks = ['VST 1', 'VST 2'];
                                    
                                    // Toggle arm state for each track
                                    tracks.forEach((trackName) => {
                                        // Find the Track
                                        let track = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
                                    
                                        // If the tracks are not armed or the global state 'AbletonTracksToArm' is less than the number of tracks in the group to iterate
                                        if (track.arm.boolValue == false || globalState['AbletonTracksToArm'] < tracks.length) {
                                            armForRecording({ track, targetValue: 'Enable' })
                                            // If the `globalState['AbletonTracksToArm']` is undefined, set to 0, 
                                            // this will select the clip slot of the first track in the following functions
                                            if (globalState['AbletonTracksToArm'] == undefined) {
                                                globalState['AbletonTracksToArm'] = 0;
                                            }
                                        } else {
                                            // If the `globalState['AbletonTracksToArm']` is equal to the number of tracks in `tracks`, 
                                            // then we disable arm and reset the global variable
                                            armForRecording({ track, targetValue: 'Disable' })
                                            if (trackName === tracks[tracks.length - 1]) {
                                                globalState['AbletonTracksToArm'] = null;
                                            }
                                        }
                                    });
                                    
                                    // If the global variable is not "null", then we arm the next free clip slot and increase the global variable by 1
                                    if (globalState['AbletonTracksToArm'] != null) {
                                        armNextFreeClipSlot({ trackName: tracks[globalState['AbletonTracksToArm']] });
                                    
                                        globalState['AbletonTracksToArm'] += 1;
                                    }
                                    
                                    1. Wwolaud @wolaud
                                        2025-06-20 03:35:54.482Z

                                        Awesome thanks so much Chad, stoked to try this out!

                                        1. Wwolaud @wolaud
                                            2025-06-25 00:56:38.424Z

                                            Hey Chad, Just want to follow up on this as I've now had a bit of time to try applying it in my session. It works great and exactly as requested, so thank you for that.

                                            In practice, I am realizing that it would be more useful if disarming the tracks was separated into two clicks: one click disarms the first track, then a second click disarms the second. All other behavior is great and would remain the same.

                                            I have tried exploring making the edits on my own but unfortunately everything I have tried seems to break the script. Is this something you could help with? Let me know!

                                            1. Chad Wahlbrink @Chad2025-06-25 14:19:44.049Z

                                              Awesome @wolaud!

                                              I'm glad it's been helpful.

                                              So I better understand, are you saying that you'd like this functionality to still work from a single button, but instead of:

                                              Button Press 1: Arm (or toggle track arm) for VST1 and VST2 and selects the first empty clip on VST1
                                              Button Press 2: Switches selection to the first empty clip slot on VST2
                                              Button Press 3: Disarm (or toggle) track arm VST1 and VST2.

                                              It would be more like:

                                              Button Press 1: Arm (or toggle track arm) for VST1 and VST2 and selects the first empty clip on VST1
                                              Button Press 2: Switches selection to the first empty clip slot on VST2
                                              Button Press 3: Disarm (or toggle) track arm VST1.
                                              Button Press 4: Disarm (or toggle) track arm VST2.

                                              --

                                              Or would you prefer to break out the Disarm functionality to a separate button

                                              Button 1:

                                              Button Press 1: Arm (or toggle track arm) for VST1 and VST2 and selects the first empty clip on VST1
                                              Button Press 2: Switches selection to the first empty clip slot on VST2

                                              Button 2:

                                              Button Press 3: Disarm (or toggle) track arm VST1.
                                              Button Press 4: Disarm (or toggle) track arm VST2.

                                              1. Wwolaud @wolaud
                                                  2025-06-25 14:24:16.214Z

                                                  Hey Chad! I meant pretty much exactly as you stated in the first example: a single a button with 4 states just like you have it laid out.

                                                  1. Chad Wahlbrink @Chad2025-06-25 15:08:33.713Z

                                                    Hi @wolaud, here's a version that should do this.

                                                    This would need to be modified more if you wanted to use it for a different number of tracks - a single track or three tracks, but this should work for a set of two tracks.

                                                    // Bail out if Ableton is not running
                                                    if (!sf.ui.abletonLive.isRunning) throw `Ableton Live is not running`;
                                                    
                                                    /**
                                                     * Arm Next Free Clip Slot
                                                     * @param {Object} params - params for function
                                                     * @param {string} params.trackName - Track to Target
                                                     */
                                                    function armNextFreeClipSlot({ trackName }) {
                                                        // Find the Track
                                                        let trackToTarget = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
                                                    
                                                        // Find the next Empty Clip Slot on the Track
                                                        let uiClipSlots = sf.ui.abletonLive.mainWindow.session.slots.groups.filter(group => group.title.value.includes(`in track ${trackName},`));
                                                        let firstEmptyClipSlot = uiClipSlots.findIndex(group => group.title.value.startsWith(`in track ${trackName}`));
                                                    
                                                        // Highlight Clip Slot
                                                        sf.app.abletonLive.song.view.highlightedClipSlot.setValue({ value: trackToTarget.clipSlots[firstEmptyClipSlot] })
                                                    }
                                                    
                                                    /**
                                                     * Arm the Track for Recording
                                                     * @param {Object} params - params for function
                                                     * @param {Object} params.track - Track to Target
                                                     * @param {'Enable'|'Disable'|'Toggle'} [params.targetValue='Toggle'] - Target Value
                                                     */
                                                    function armForRecording({ track, targetValue = 'Toggle' }) {
                                                        if (targetValue === 'Toggle') {
                                                            let currentArmState = track.arm.value;
                                                            track.arm.setValue({ value: !currentArmState });
                                                        } else if (targetValue === 'Enable') {
                                                            // Arm The Track
                                                            track.arm.setValue({ value: true });
                                                        } else if (targetValue === 'Disable') {
                                                            // Disarm The Track
                                                            track.arm.setValue({ value: false });
                                                        }
                                                    }
                                                    
                                                    // Track Names
                                                    const tracks = ['VST 1', 'VST 2'];
                                                    
                                                    // Initialize state
                                                    if (globalState['AbletonTracksToArm'] === undefined || globalState['AbletonTracksToArm'] === null) {
                                                        globalState['AbletonTracksToArm'] = 0;
                                                    }
                                                    
                                                    switch (globalState['AbletonTracksToArm']) {
                                                        case 0: {
                                                            // Press 1: Arm both tracks, select clip on VST 1
                                                            tracks.forEach(trackName => {
                                                                const track = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
                                                                armForRecording({ track, targetValue: 'Enable' });
                                                            });
                                                            armNextFreeClipSlot({ trackName: tracks[0] });
                                                            globalState['AbletonTracksToArm'] = 1;
                                                            break;
                                                        }
                                                        case 1: {
                                                            // Press 2: Select clip on VST 2
                                                            armNextFreeClipSlot({ trackName: tracks[1] });
                                                            globalState['AbletonTracksToArm'] = 2;
                                                            break;
                                                        }
                                                        case 2: {
                                                            // Press 3: Disarm VST 1
                                                            const track = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === tracks[0]);
                                                            armForRecording({ track, targetValue: 'Disable' });
                                                            globalState['AbletonTracksToArm'] = 3;
                                                            break;
                                                        }
                                                        case 3: {
                                                            // Press 4: Disarm VST 2 and reset
                                                            const track = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === tracks[1]);
                                                            armForRecording({ track, targetValue: 'Disable' });
                                                            globalState['AbletonTracksToArm'] = null;
                                                            break;
                                                        }
                                                    }
                                                    
                                                    1. Chad Wahlbrink @Chad2025-06-25 15:11:40.805Z

                                                      A single track could be handled like this. Note that all the global state variables are changed to a new value here: globalState['AbletonTrackArmState-VST 1']

                                                      Each track you want to set up like this should have a unique global state variable

                                                      if (!sf.ui.abletonLive.isRunning) throw `Ableton Live is not running`;
                                                      
                                                      const trackName = 'VST 1';
                                                      
                                                      /**
                                                       * Select first empty clip slot for given track name
                                                       */
                                                      function armNextFreeClipSlot({ trackName }) {
                                                          const track = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
                                                          const uiClipSlots = sf.ui.abletonLive.mainWindow.session.slots.groups.filter(group =>
                                                              group.title.value.includes(`in track ${trackName},`)
                                                          );
                                                          const firstEmptyClipSlot = uiClipSlots.findIndex(group =>
                                                              group.title.value.startsWith(`in track ${trackName}`) && group.children.length === 0
                                                          );
                                                          if (firstEmptyClipSlot >= 0) {
                                                              sf.app.abletonLive.song.view.highlightedClipSlot.setValue({
                                                                  value: track.clipSlots[firstEmptyClipSlot]
                                                              });
                                                          }
                                                      }
                                                      
                                                      /**
                                                       * Arm or disarm the given track
                                                       */
                                                      function armForRecording({ track, targetValue = 'Toggle' }) {
                                                          const currentState = track.arm.value;
                                                          if (targetValue === 'Toggle') {
                                                              track.arm.setValue({ value: !currentState });
                                                          } else if (targetValue === 'Enable') {
                                                              track.arm.setValue({ value: true });
                                                          } else if (targetValue === 'Disable') {
                                                              track.arm.setValue({ value: false });
                                                          }
                                                      }
                                                      
                                                      // Initialize or increment state
                                                      if (globalState['AbletonTrackArmState-VST 1'] === undefined || globalState['AbletonTrackArmState-VST 1'] === null) {
                                                          globalState['AbletonTrackArmState-VST 1'] = 0;
                                                      }
                                                      
                                                      const track = sf.app.abletonLive.song.tracks.find(tr => tr.name.stringValue === trackName);
                                                      
                                                      switch (globalState['AbletonTrackArmState-VST 1']) {
                                                          case 0: {
                                                              // Press 1: Arm and select first empty clip slot
                                                              armForRecording({ track, targetValue: 'Enable' });
                                                              armNextFreeClipSlot({ trackName });
                                                              globalState['AbletonTrackArmState-VST 1'] = 1;
                                                              break;
                                                          }
                                                          case 1: {
                                                              // Press 2: Disarm
                                                              armForRecording({ track, targetValue: 'Disable' });
                                                              globalState['AbletonTrackArmState-VST 1'] = null;
                                                              break;
                                                          }
                                                      }
                                                      
                                                      1. In reply toChad:
                                                        Wwolaud @wolaud
                                                          2025-07-09 17:11:47.879Z

                                                          Hey Chad, Just wanted to follow up and say this script is working great for me — thanks so much for helping me get to this solution.

                                                          I did have a thought this morning, and apologies in advance as I'm sure this will introduce some complications: Would it be possible to use this script as it's written but to also first CREATE the tracks it references?

                                                          So, in this instance, VST 1 and VST 2 would not exist yet in the session - running the script would first create these tracks (in practice it would be pairs of MIDI and audio tracks with specific routings and devices on each track) and then toggle through selecting / arming the tracks just as we have it set up above.

                                                          Let me know if something like this would be possible and if so what would be the easiest way to cobble something like this together.

                                                          1. Chad Wahlbrink @Chad2025-07-10 03:17:46.702Z

                                                            Hi, @wolaud,

                                                            Awesome idea! When you say pairs of MIDI and audio tracks, do you mean that the script would create two audio and two MIDI tracks at once (four tracks total), or do you mean that you'd use this idea as a template for creating pairs of tracks to be used in this manner?

                                                            It should be doable, I'll take a look in the next couple of days!

                                                            1. Wwolaud @wolaud
                                                                2025-07-10 03:40:46.475Z

                                                                Hey Chad, thanks for the response. I work in Ableton with quite a bit of hardware so my typical workflow involves two tracks for each piece of gear:

                                                                1 - MIDI track with the Ableton External Instrument device on it (plus various saved presets for controller devices with knobs mapped MIDI CCs on the hardware)
                                                                2 - An Audio track that is set to receive the incoming audio signal from the MIDI track

                                                                You can see in these screenshots how the typical setup works: OB6 MIDI receives MIDI data, pipes it out to hardware and receives the incoming audio signal while OB6 Audio receives, monitors and records the audio output from OB6 MIDI.

                                                                The way we had been working previously on scripts that toggle tracks on and off was to help me streamline the process of arming and recording MIDI, then arming and recording audio, then disarming both tracks to move on to the next part part. This works great, but it only works if the tracks already exist in the session.

                                                                So ideally I would be able to start with a much more minimal template, and when I trigger the script it checks to see if the channels already exist — and if not, it creates them with the appropriate devices and track input settings.

                                                                I tried cobbling something together with existing Macros but it doesn't seem to work with the script you wrote for me earlier in this thread (probably for obvious reasons to you, not so much to me unfortunately haha.)

                                                                Here is as far as I got — if nothing else it will show you the settings that are most useful to me for each track:

                                                                
                                                                //Calling command "Insert MIDI Track" from package "Ableton Live" (installed from user/pkg/version "oMDTMcbKv2OT4GaE3NFzdjMdeXj2/clrm7ovg00000wm10jrwimta1/cmamkgdi700001510iefadfp6")
                                                                sf.soundflow.runCommand({
                                                                    commandId: 'user:clrm7ovg00000wm10jrwimta1:clzvtgqus0048qc10og7hdcti#clzvtgqus003rqc101k4uy6yc',
                                                                    props: {}
                                                                });
                                                                
                                                                //Calling command "Load Devices" from package "Ableton Live" (installed from user/pkg/version "oMDTMcbKv2OT4GaE3NFzdjMdeXj2/clrm7ovg00000wm10jrwimta1/cmamkgdi700001510iefadfp6")
                                                                sf.soundflow.runCommand({
                                                                    commandId: 'user:clrm7ovg00000wm10jrwimta1:cm60x93180002ds10g76s6knl',
                                                                    props: {
                                                                        browserPath: ["User Library","OB6 CTRL.adg"],
                                                                    }
                                                                });
                                                                
                                                                //Calling command "Rename" from package "Ableton Live" (installed from user/pkg/version "oMDTMcbKv2OT4GaE3NFzdjMdeXj2/clrm7ovg00000wm10jrwimta1/cmamkgdi700001510iefadfp6")
                                                                sf.soundflow.runCommand({
                                                                    commandId: 'user:clrm7ovg00000wm10jrwimta1:clzvtgqus003pqc10fbu64ga9#clzvtgqus0030qc10vubk0vfm',
                                                                    props: {}
                                                                });
                                                                
                                                                sf.keyboard.type({
                                                                    text: "OB6 MIDI",
                                                                });
                                                                
                                                                sf.keyboard.press({
                                                                    keys: "return",
                                                                });
                                                                
                                                                //Calling command "Toggle Activate Track" from package "Ableton Live" (installed from user/pkg/version "oMDTMcbKv2OT4GaE3NFzdjMdeXj2/clrm7ovg00000wm10jrwimta1/cmamkgdi700001510iefadfp6")
                                                                sf.soundflow.runCommand({
                                                                    commandId: 'user:clrm7ovg00000wm10jrwimta1:clzvtgqus003pqc10fbu64ga9#cm8gnrs8x000gur10n1n8jlot',
                                                                    props: {}
                                                                });
                                                                
                                                                //Calling command "Arm Track" from package "Ableton Live" (installed from user/pkg/version "oMDTMcbKv2OT4GaE3NFzdjMdeXj2/clrm7ovg00000wm10jrwimta1/cmamkgdi700001510iefadfp6")
                                                                sf.soundflow.runCommand({
                                                                    commandId: 'user:clrm7ovg00000wm10jrwimta1:clzvtgqus003pqc10fbu64ga9#clzvtgqus0033qc10tt99283k',
                                                                    props: {}
                                                                });
                                                                
                                                                //Calling command "Insert Audio Track" from package "Ableton Live" (installed from user/pkg/version "oMDTMcbKv2OT4GaE3NFzdjMdeXj2/clrm7ovg00000wm10jrwimta1/cmamkgdi700001510iefadfp6")
                                                                sf.soundflow.runCommand({
                                                                    commandId: 'user:clrm7ovg00000wm10jrwimta1:clzvtgqus0048qc10og7hdcti#clzvtgqus003qqc103d6c61r4',
                                                                    props: {}
                                                                });
                                                                
                                                                //Calling command "Rename" from package "Ableton Live" (installed from user/pkg/version "oMDTMcbKv2OT4GaE3NFzdjMdeXj2/clrm7ovg00000wm10jrwimta1/cmamkgdi700001510iefadfp6")
                                                                sf.soundflow.runCommand({
                                                                    commandId: 'user:clrm7ovg00000wm10jrwimta1:clzvtgqus003pqc10fbu64ga9#clzvtgqus0030qc10vubk0vfm',
                                                                    props: {}
                                                                });
                                                                
                                                                sf.keyboard.type({
                                                                    text: "OB6 AUDIO",
                                                                });
                                                                
                                                                sf.keyboard.press({
                                                                    keys: "return",
                                                                });
                                                                
                                                                //Calling command "Set Input" from package "Ableton Live" (installed from user/pkg/version "oMDTMcbKv2OT4GaE3NFzdjMdeXj2/clrm7ovg00000wm10jrwimta1/cmamkgdi700001510iefadfp6")
                                                                sf.soundflow.runCommand({
                                                                    commandId: 'user:clrm7ovg00000wm10jrwimta1:cm9tb6tp70000pd10epk31cz6',
                                                                    props: {
                                                                        inputType: "OB6 MIDI",
                                                                        inputChannel: "Post FX",
                                                                    }
                                                                });
                                                                
                                                                //Calling command "Set Monitoring Mode for Selected Track" from package "Ableton Live" (installed from user/pkg/version "oMDTMcbKv2OT4GaE3NFzdjMdeXj2/clrm7ovg00000wm10jrwimta1/cmamkgdi700001510iefadfp6")
                                                                sf.soundflow.runCommand({
                                                                    commandId: 'user:clrm7ovg00000wm10jrwimta1:cm5wn8upk000513100xlotjw6',
                                                                    props: {
                                                                        currentMonitoringState: "1",
                                                                    }
                                                                });
                                                                
                                                                //Calling command "Arm Track" from package "Ableton Live" (installed from user/pkg/version "oMDTMcbKv2OT4GaE3NFzdjMdeXj2/clrm7ovg00000wm10jrwimta1/cmamkgdi700001510iefadfp6")
                                                                sf.soundflow.runCommand({
                                                                    commandId: 'user:clrm7ovg00000wm10jrwimta1:clzvtgqus003pqc10fbu64ga9#clzvtgqus0033qc10tt99283k',
                                                                    props: {}
                                                                });
                                                                
                                                                1. Chad Wahlbrink @Chad2025-07-10 03:49:57.469Z

                                                                  Thanks for the clarification, @wolaud! I'll give this a shot in the next few days. Cheers.

                                    2. In reply towolaud:
                                      Chad Wahlbrink @Chad2025-06-13 16:54:36.437Z

                                      Hi @wolaud,

                                      To toggle exclusive arm, I would use this script:

                                      
                                      // Bail out if Ableton is not running
                                      if (!sf.ui.abletonLive.isRunning) throw `Ableton Live is not running`;
                                      
                                      // Define the Settings Window
                                      let settingsWin = sf.ui.abletonLive.invalidate().windows.whoseTitle.is("Settings").first;
                                      let settingsWinExisted = settingsWin.exists;
                                      
                                      // If the Settings Window Isn't On Screen, then Open it
                                      if (!settingsWinExisted) {
                                          sf.ui.abletonLive.menuClick({ menuPath: ['Live', 'Settings...'] });
                                          settingsWin.elementWaitFor();
                                      }
                                      
                                      // Select the 'Record, Warp & Launch' tab
                                      sf.ui.abletonLive.mainWindow.groups.first.radioGroups.whoseTitle.is("Settings Page Chooser").first.radioButtons.whoseDescription.is("Record, Warp & Launch").first.elementClick();
                                      
                                      // Toggle Exclusive Arm
                                      sf.ui.abletonLive.mainWindow.groups.first.groups.whoseTitle.is("Record, Warp and Launch Settings").first.checkBoxes.whoseDescription.is("Exclusive Arm").first.checkboxSet({ targetValue: "Toggle" });
                                      
                                      // Close the Settings Window if it wasn't already open
                                      if (!settingsWinExisted) {
                                          sf.ui.abletonLive.mainWindow.closeButton.elementClick();
                                      }
                                      
                                      ReplySolution
                                      1. In reply towolaud:
                                        Chad Wahlbrink @Chad2025-06-13 17:12:49.533Z2025-06-13 17:46:16.185Z

                                        To set the fade mode for an audio clip, you can use this.

                                        This will be included in the next version of the official package as well!

                                        
                                        let clipViewTab = "Tool Tabs"
                                        let horizontalOrientationName = "Audio Utilities"
                                        let verticalOrientationName = "SampleTools"
                                        let checkBox = "Clip Fade-In/Fade-Out"
                                        let targetValue = "Toggle"
                                        
                                        // Click the Audio Clip View Button
                                        setAudioClipViewCheckBox({
                                            clipViewTab,
                                            horizontalOrientationName,
                                            verticalOrientationName,
                                            checkBox,
                                            targetValue
                                        })
                                        
                                        /**
                                         * Navigate to Clip View Tab
                                         * @param {Object} props 
                                         * @param {Object} props.radioGroup
                                         * @param {string} props.targetRadioButtonName
                                         */
                                        function navigateToClipViewTab({ radioGroup, targetRadioButtonName }) {
                                        
                                            const radioButtons = radioGroup.radioButtons;
                                        
                                            // Find current radio button (by intValue === 1)
                                            const currentRadioButton = radioButtons.find(rb => rb.value.intValue === 1);
                                            if (!currentRadioButton) throw new Error("Current radio button not found");
                                        
                                            const currentName = currentRadioButton.getString("AXDescription");
                                        
                                            // Find indices of current and target radio buttons
                                            const currentIndex = radioButtons.findIndex(rb => rb.getString("AXDescription") === currentName);
                                            const targetIndex = radioButtons.findIndex(rb => rb.getString("AXDescription") === targetRadioButtonName);
                                        
                                            if (targetIndex === -1) throw new Error(`Target button '${targetRadioButtonName}' not found in radio group`);
                                        
                                            // 
                                            const delta = targetIndex - currentIndex;
                                        
                                            if (delta === 0) return; // Already on the target
                                        
                                            // Focus the current button so that it accepts arrow key presses
                                            currentRadioButton.elementSetAttributeValue({
                                                attributeName: "AXFocused",
                                                attributeValue: true,
                                            });
                                        
                                            const directionKey = delta > 0 ? "kVK_RightArrow" : "kVK_LeftArrow";
                                        
                                            for (let i = 0; i < Math.abs(delta); i++) {
                                                sf.keyboard.internalPress({ virtualKey: directionKey });
                                        
                                                // Maybe add a waitFor to make sure the intValue has changed for the target radio button.
                                            }
                                        }
                                        
                                        /**
                                         * Set Audio Clip View checkBox
                                         */
                                        function setAudioClipViewCheckBox({ clipViewTab, horizontalOrientationName, verticalOrientationName, checkBox, targetValue }) {
                                        
                                            // Activate Ableton
                                            sf.ui.abletonLive.appActivateMainWindow();
                                        
                                            const mainWindow = sf.ui.abletonLive.mainWindow;
                                        
                                            const clipDetailGroup = mainWindow.groups.first.groups.whoseTitle.is("Clip Detail").first;
                                            const clipTitleBar = clipDetailGroup.groups.whoseTitle.is("Clip Title Bar").first;
                                            const radioGroup = clipDetailGroup.radioGroups.whoseTitle.is(clipViewTab).first;
                                        
                                            // If Clip View is Not Shown, Then Show It
                                            if (!sf.ui.abletonLive.getMenuItem('View', 'Clip View').isMenuChecked) {
                                                sf.ui.abletonLive.menuClick({ menuPath: ['View', 'Clip View'] })
                                            }
                                        
                                            // If no clip is selected then bail
                                            if (clipDetailGroup.children.whoseRole.is("AXStaticText").whoseValue.is("No clip selected.").first.exists) {
                                                log(`No Clip Selected`)
                                                throw 0;
                                            }
                                        
                                            // If this is not a Audio clip then, we should bail! 
                                            if (!sf.app.abletonLive.song.view.detailClip.invalidate().isAudioClip.boolValue) {
                                                log(`This Is Not An Audio Clip`);
                                                throw 0;
                                            }
                                        
                                            function showHideClipProperties(targetValue) {
                                                if (clipTitleBar.exists) {
                                                    clipTitleBar.elementClick({ actionName: 'AXShowMenu' })
                                        
                                                    // Explicitly define the context menu
                                                    let contextMenuWin = sf.ui.abletonLive.windows.whoseTitle.is('').allItems.find(window => window.groups.first.groups.whoseTitle.is('Context').exists)
                                                        .groups.first.groups.whoseTitle.is("Context").first;
                                        
                                                    let foldMenuItem = contextMenuWin.menuItems.whoseTitle.endsWith('Fold').first;
                                        
                                                    // Wait for the context menu to show up
                                                    contextMenuWin.elementWaitFor({}, `Failed Waiting for the context menu`);
                                        
                                                    if (foldMenuItem.exists && foldMenuItem.title.invalidate().value == 'Fold' && (targetValue == "Disable" || targetValue == "Toggle")) {
                                                        foldMenuItem.elementClick({ actionName: "AXPick" });
                                                    } else if (foldMenuItem.title.invalidate().value == '✔ Fold' && (targetValue == "Enable" || targetValue == "Toggle")) {
                                                        contextMenuWin.menuItems.whoseTitle.is('✔ Fold').first.elementClick({ actionName: "AXPick" })
                                                    } else {
                                                        //dismiss menu
                                                        sf.keyboard.internalPress({ virtualKey: `kVK_Escape` });
                                                    }
                                                }
                                            }
                                        
                                            let werePropsHid = (!radioGroup.invalidate().radioButtons.exists)
                                                && (!clipDetailGroup.checkBoxes.whoseDescription.is(verticalOrientationName).first.exists);
                                        
                                            // Make sure the clip view panel is shown in some form - it's going to default to vertical
                                            if (werePropsHid) {
                                                showHideClipProperties('Enable')
                                            }
                                        
                                            // In horizontal mode, make sure we are on the right tool tab
                                            if (radioGroup.radioButtons.whoseDescription.is(horizontalOrientationName).first.exists
                                                && radioGroup.radioButtons.whoseDescription.is(horizontalOrientationName).first.value.intValue == 0) {
                                        
                                                navigateToClipViewTab({
                                                    radioGroup,
                                                    targetRadioButtonName: horizontalOrientationName
                                                });
                                        
                                            }
                                            // If we are in vertical mode, ensure the tab is open.
                                            else if (clipDetailGroup.checkBoxes.whoseDescription.is(verticalOrientationName).first.exists) {
                                                clipDetailGroup.checkBoxes.whoseDescription.is(verticalOrientationName).first.checkboxSet({ targetValue: "Enable" })
                                            }
                                            // If clip details are shown and neither version of the Sample Tools clip view exists, this is not an audio clip. 
                                            else if (!radioGroup.radioButtons.whoseDescription.is(horizontalOrientationName).first.exists
                                                && !clipDetailGroup.checkBoxes.whoseDescription.is(verticalOrientationName).first.exists
                                                && !sf.app.abletonLive.song.view.detailClip.invalidate().isAudioClip.boolValue) {
                                                log(`This Is Not An Audio Clip`);
                                                throw 0;
                                            }
                                        
                                            if (checkBox == 'Clip Fade-In/Fade-Out' && !clipDetailGroup.checkBoxes.whoseDescription.is(checkBox).first.exists) {
                                                throw `Clip Fade-In/Fade-Out is only available in Session View.`
                                            }
                                        
                                            // Set The CheckBox
                                            clipDetailGroup.checkBoxes.whoseDescription.is(checkBox).first.checkboxSet({ targetValue: targetValue })
                                        
                                        
                                            if (werePropsHid) {
                                                showHideClipProperties('Disable')
                                            }
                                        }