Sub-Dividing arrays for Podcast Editing Script
Hey everybody,
Does anybody know of simple way (hopefully just a method) to subdivide an array within itself?
I’m working on something for podcast editors in which, after strip-silencing away the “dead space,” we’d slide the clips over without disturbing the conversational timings between speakers. Assume each speaker has their own microphone and track. The net effect would be an identical conversation without any of the extra pauses or “umms” that tend to add 15 minutes of nothingness to an hour-long show.
In layman’s explanation, I am collecting all of the information about the selected clips at the very beginning and sorting them them by Start time. TrackName is one of the criteria that also pops up when collecting this data. After data collection, I’d like to sub-divide the array into smaller sections every time the “Track Name” changes. I'm referring to this as a "clip set" in my code.
I believe that I have the “action” function done, but without sub-dividing the grand array into "clip sets", the double-layer index's I'm trying to target (as clipsets) obviously don't exist for my action function.
This is my current progress:
function promptClipDistance() { // Prompt for clip spacing in seconds
const iconPath = `/Users/randybrown/Documents/Professional Materials/SoundFlowRandyIcon.png`
const iconExists = sf.file.exists({ path: iconPath }).exists
let spacingInSeconds = sf.interaction.displayDialog({
prompt: 'Please set your clip spacing (in seconds):',
title: 'Clear the Air',
defaultAnswer: "0.25", // Default to 25 milliseconds
hiddenAnswer: false,
icon: iconExists ? iconPath : undefined
}).text
if (spacingInSeconds === '') {
throw 'Please Set a Clip Spacing in Seconds'
}
//Converts Seconds to Samples for easy entry
const sampleRate = sf.app.proTools.getSessionSampleRate().sampleRateNumerical;
const clipSpacing = parseFloat(spacingInSeconds) * sampleRate;
return clipSpacing;
}
function createClipsArray() {
let clips = Array.from(sf.app.proTools.getSelectedClipInfo().clips);
clips.sort((a, b) => Number(a.startTime) - Number(b.startTime));
return clips;
}
function spaceClips(time) {
sf.keyboard.press({ keys: "alt+shift+h" }); //Open Space Clips
sf.ui.proTools.windows.whoseTitle.is("Space Clips").first.elementWaitFor()
sf.ui.proTools.windows.whoseTitle.is("Space Clips").first.popupButtons.first.popupMenuSelect({ menuPath: ["Samples"] });
const win = sf.ui.proTools.windows.whoseTitle.is("Space Clips").first;
let spaceField = win.textFields.whoseTitle.is("NumericEntryText").allItems[0];
win.elementWaitFor();
if (spaceField.value.invalidate().value.trim() !== time) {
spaceField.elementClick();
sf.keyboard.press({ keys: "backspace" });
sf.keyboard.type({ text: time });
let i = 0;
while (spaceField.value.invalidate().value.trim() !== time) {
sf.wait({ intervalMs: 100 });
i++;
if (i >= 20) throw `Could not update number field`;
}
sf.ui.proTools.windows.whoseTitle.is("Space Clips").first.buttons.whoseTitle.is("Apply").first.mouseClickElement();
win.windowClose();
win.elementWaitFor({ waitType: "Disappear" });
}
}
function processClipSets(masterArray, spacing) {
/* The first and second clip offset set needs to be processed on its own. When utilizing the final clipset, we can't
be using (i+1) */
let firstClipSet = masterArray[0]
let secondClipSet = masterArray[1]
let secondClipSetStart = secondClipSet[0].startTime
let firstClipSetEnd = firstClipSet[firstClipSet.length].endTime
let relativeOffset = Number(secondClipSetStart) - Number(firstClipSetEnd);
//Target First Track for processing
sf.app.proTools.tracks.invalidate()
sf.app.proTools.selectTracksByName({ trackNames: firstClipSet[0].trackName, selectionMode: "Replace" });
//Target First Set of Clips
sf.app.proTools.setTimelineSelection({ inTime: firstClipSet[0].startTime, outTime: firstClipSet[firstClipSet.length].endTime })
//Execute
spaceClips(spacing);
//Grab end of NEWLY CREATED selection. This will be the launch-point for the relative paste
let newTrackAnchorPoint = sf.app.proTools.getTimelineSelection().outTime //Update, will need for offset
let secondClipSetPastePoint = Number(newTrackAnchorPoint) + relativeOffset //Now that first clipset was reorganzied, this is the adjusted start point for next clip set.
//Target second clip set Track
sf.app.proTools.tracks.invalidate()
sf.app.proTools.selectTracksByName({ trackNames: secondClipSet[0].trackName, selectionMode: "Replace" });
//Target clips (haven't been moved yet)
sf.app.proTools.setTimelineSelection({ inTime: secondClipSetStart, outTime: secondClipSet[secondClipSet.length].endTime })
//Paste Second Clipset to Start in its new location
sf.app.proTools.cut()
sf.app.proTools.setTimelineSelection({ inTime: secondClipSetPastePoint.toString() })
sf.app.proTools.paste()
//Second clip set adjusted. Timeline now has a new anchorpoint
spaceClips(spacing)
//Loop starts at 3rd clip set
for (let i = 2; i < masterArray.length; i++) {
newTrackAnchorPoint = sf.app.proTools.getTimelineSelection().outTime
const currentClipSet = masterArray[i]
const previousClipSet = masterArray[i - 1]
let currentClipSetStart = currentClipSet[0].startTime
let previousClipSetEnd = previousClipSet[previousClipSet.length].endTime
let relativeOffset = Number(currentClipSetStart) - Number(previousClipSetEnd);
let pastePoint = Number(newTrackAnchorPoint) + relativeOffset //Now that first clipset was reorganzied, this is the adjusted start point for next clip set.
//Target Track. Track data is being pulled from the first clip data in the current clipset.
sf.app.proTools.selectTracksByName({ trackNames: currentClipSet[0].trackName, selectionMode: "Replace" })
//Target the Clips to
sf.app.proTools.setTimelineSelection({ inTime: currentClipSet[0].startTime, outTime: currentClipSet[currentClipSet.length].endTime })
sf.app.proTools.cut()
sf.app.proTools.setTimelineSelection({ inTime: pastePoint.toString() })
sf.app.proTools.paste()
spaceClips(spacing)
}
}
function main() {
sf.ui.proTools.appActivateMainWindow();
sf.app.proTools.setMainCounterFormat({ value: "Samples" })
//Enables Edit/Timeline Link
sf.soundflow.runCommand({ commandId: 'user:ckp49i4j60000a2100yfwywgf:cksb6kui000147g10vv94f9t2#cks0ssycu0007hu10mpyprc8d' })
let clips = createClipsArray();
let spacing = promptClipDistance();
//HELP HERE! I need a function before this that splits the clips array into chunks (clipsets) every time the speaker/track changes. These sub-arrays will be used to make the following "for loop" work.
processClipSets(clips, spacing);
sf.app.proTools.setMainCounterFormat({ value: "MinSecs" }); //Sets back to min/secs because it's most helpful for podcast editing.
}
main();
Would appreciate any help! I think we can save people some serious time with this one and thanks in advance!
- Kitch Membery @Kitch2025-04-04 18:05:47.051Z
Hi @Randy_Brown
Can you provide an example of how you want the Array to look before and after?
Randy Brown @Randy_Brown
On its way! Thanks for the speedy reply!
- In reply toKitch⬆:
Randy Brown @Randy_Brown
Grand Clip Set:
[ { "TrackName": "Speaker 1", "ClipName": "Yamaha SY_01-01", "StartTime": 0, "EndTime": 6029312, "startTime": 0 }, { "TrackName": "Speaker 2", "ClipName": "Yamaha SY_01-02", "StartTime": 4194304, "EndTime": 8126464, "startTime": 4194304 }, { "TrackName": "Speaker 2", "ClipName": "Yamaha SY_01-04", "StartTime": 8650752, "EndTime": 11534336, "startTime": 8650752 }, { "TrackName": "Speaker 3", "ClipName": "Yamaha SY_01-05", "StartTime": 9699328, "EndTime": 14155776, "startTime": 9699328 }, { "TrackName": "Speaker 1", "ClipName": "Yamaha SY_01-10", "StartTime": 12288000, "EndTime": 18087936, "startTime": 12288000 }, { "TrackName": "Speaker 2", "ClipName": "Yamaha SY_01-08", "StartTime": 15204352, "EndTime": 41680896, "startTime": 15204352 }, { "TrackName": "Speaker 1", "ClipName": "Yamaha SY_01-11", "StartTime": 40108032, "EndTime": 42467328, "startTime": 40108032 } ]
Adjusted Clipsets:
[ [ //ClipSet 1 { "TrackName": "Speaker 1", "ClipName": "Yamaha SY_01-01", "StartTime": 0, "EndTime": 6029312, "startTime": 0 } ], [ //ClipSet 2 { "TrackName": "Speaker 2", "ClipName": "Yamaha SY_01-02", "StartTime": 4194304, "EndTime": 8126464, "startTime": 4194304 }, { "TrackName": "Speaker 2", "ClipName": "Yamaha SY_01-04", "StartTime": 8650752, "EndTime": 11534336, "startTime": 8650752 } ], [ //ClipSet 3 { "TrackName": "Speaker 3", "ClipName": "Yamaha SY_01-05", "StartTime": 9699328, "EndTime": 14155776, "startTime": 9699328 } ], [ //Clip Set 4 { "TrackName": "Speaker 1", "ClipName": "Yamaha SY_01-10", "StartTime": 12288000, "EndTime": 18087936, "startTime": 12288000 } ], [ //Clip Set 4 { "TrackName": "Speaker 2", "ClipName": "Yamaha SY_01-08", "StartTime": 15204352, "EndTime": 41680896, "startTime": 15204352 } ], [ //ClipSet 5 { "TrackName": "Speaker 1", "ClipName": "Yamaha SY_01-11", "StartTime": 40108032, "EndTime": 42467328, "startTime": 40108032 } ]
I believe I need my arrays to look like the above. I'm adding brackets every time the TrackName changes as a way of sub-diving the current speaker's uninterrupted part of the conversation. I hate to say punching this into my own script for testing is revealing other bugs in my action script, but I do believe that I should lock down how these arrays will switch before I can comfortably actualize the debugging.
In the for loop, the hope would be identifying the currently targeted clip set with something akin to grandArray[0][i].
Getting the "Track Name" of that clip set would be something like "grandArray[0][i][0].trackName in which I have to access data from the first clip inside that subdivided arrayThanks and please let me know if I can give further explanation!
Kitch Membery @Kitch2025-04-04 19:23:18.314Z
Ok I understand now. I misread your initial post. I will reply shortly.
- In reply toRandy_Brown⬆:Kitch Membery @Kitch2025-04-04 18:26:38.098Z
Hi @Randy_Brown
If you're just needing to split the array up into chunks of a specific length... You could use something like this.
function chunkArray(array, chunkSize) { const chunks = []; for (let i = 0; i < array.length; i += chunkSize) { chunks.push(array.slice(i, i + chunkSize)); } return chunks; } const myArray = ["A", "B", "C", "D", "E", "F", "G"]; const chunks = chunkArray(myArray, 3); log(chunks);
The output will be
[ [ "A", "B", "C" ], [ "D", "E", "F" ], [ "G" ] ]
Randy Brown @Randy_Brown
I'm actually hoping there's a way to do this by looping through the GrandArray and seeing if (current clip).trackName === (previous clip).trackName to determine when a bracket would be inserted
I unfortunately wont be able to count on every few clips being of the same speaker, it's going to be very conversation dependent.
- In reply toRandy_Brown⬆:Kitch Membery @Kitch2025-04-04 20:56:41.787Z
Hi @Randy_Brown
See below...
The function
groupClipSets
takes an array of clips (items) and groups consecutive clips that have the same "TrackName" into subarrays.Every time the TrackName changes (compared to the previous item), it starts a new group.
function groupClipSets(clips) { const clipSets = []; let currentGroup = []; for (let i = 0; i < clips.length; i++) { const currentClip = clips[i]; const prevClip = clips[i - 1]; if (i === 0 || currentClip["TrackName"] === prevClip["TrackName"]) { currentGroup.push(currentClip); } else { clipSets.push(currentGroup); currentGroup = [currentClip]; } } if (currentGroup.length > 0) { clipSets.push(currentGroup); } return clipSets; } const items = [ { "TrackName": "Speaker 1", "ClipName": "Yamaha SY_01-01", "StartTime": 0, "EndTime": 6029312, "startTime": 0 }, { "TrackName": "Speaker 2", "ClipName": "Yamaha SY_01-02", "StartTime": 4194304, "EndTime": 8126464, "startTime": 4194304 }, { "TrackName": "Speaker 2", "ClipName": "Yamaha SY_01-04", "StartTime": 8650752, "EndTime": 11534336, "startTime": 8650752 }, { "TrackName": "Speaker 3", "ClipName": "Yamaha SY_01-05", "StartTime": 9699328, "EndTime": 14155776, "startTime": 9699328 }, { "TrackName": "Speaker 1", "ClipName": "Yamaha SY_01-10", "StartTime": 12288000, "EndTime": 18087936, "startTime": 12288000 }, { "TrackName": "Speaker 2", "ClipName": "Yamaha SY_01-08", "StartTime": 15204352, "EndTime": 41680896, "startTime": 15204352 }, { "TrackName": "Speaker 1", "ClipName": "Yamaha SY_01-11", "StartTime": 40108032, "EndTime": 42467328, "startTime": 40108032 } ]; const clipSets = groupClipSets(items); log(clipSets);
Let me know if that works for you. :-)
Kitch Membery @Kitch2025-04-04 20:58:44.626Z
Hi @Randy_Brown,
The logged output from the above script is...
[ [ { "TrackName": "Speaker 1", "ClipName": "Yamaha SY_01-01", "StartTime": 0, "EndTime": 6029312, "startTime": 0 } ], [ { "TrackName": "Speaker 2", "ClipName": "Yamaha SY_01-02", "StartTime": 4194304, "EndTime": 8126464, "startTime": 4194304 }, { "TrackName": "Speaker 2", "ClipName": "Yamaha SY_01-04", "StartTime": 8650752, "EndTime": 11534336, "startTime": 8650752 } ], [ { "TrackName": "Speaker 3", "ClipName": "Yamaha SY_01-05", "StartTime": 9699328, "EndTime": 14155776, "startTime": 9699328 } ], [ { "TrackName": "Speaker 1", "ClipName": "Yamaha SY_01-10", "StartTime": 12288000, "EndTime": 18087936, "startTime": 12288000 } ], [ { "TrackName": "Speaker 2", "ClipName": "Yamaha SY_01-08", "StartTime": 15204352, "EndTime": 41680896, "startTime": 15204352 } ], [ { "TrackName": "Speaker 1", "ClipName": "Yamaha SY_01-11", "StartTime": 40108032, "EndTime": 42467328, "startTime": 40108032 } ] ]
- In reply toKitch⬆:
Randy Brown @Randy_Brown
Ahhh I see! Instead of jabbing brackets into my array, we feed the info to a “current set,” then dump it out to a fresh array when the track changes. Very clever and should definitely do it!! Thanks so so much!
Will update this post with a final version once I get everything going!