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=
- Wwolaud @wolaud
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!
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!
- Wwolaud @wolaud
Thanks Chad! I'll keep an eye out for your updates and reach out about getting a Zoom scheduled.
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() } });
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:
- 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();
- Wwolaud @wolaud
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.
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.
- Wwolaud @wolaud
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!
- Wwolaud @wolaud
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?
- Wwolaud @wolaud
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.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' });
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.- Wwolaud @wolaud
Thanks Chad.
I think automatic is best if it's possible, but all good if modifiers are the only way to pull it off.
- 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; }
- Wwolaud @wolaud
Awesome thanks so much Chad, stoked to try this out!
- Wwolaud @wolaud
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!
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 VST2Button 2:
Button Press 3: Disarm (or toggle) track arm VST1.
Button Press 4: Disarm (or toggle) track arm VST2.- Wwolaud @wolaud
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.
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; } }
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; } }
- In reply toChad⬆:Wwolaud @wolaud
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.
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!
- Wwolaud @wolaud
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 trackYou 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: {} });
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.
- 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(); }
- 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') } }