Create Tempo Map in Pro Tools and Export it as .csv File
Desired Workflow
GOAL: Create Tempo Map in Pro Tools and Export it as .csv File
-
activate Pro Tools
-
set Main Counter to “Min:Sec”
-
set Sub Counter to “Bars|Beats”
-
Select "CLICK" Track
Start process to find number of bars in the song
Separate “CLICK” track clip at Transients
-
Click Menu Item: Edit > Separate Clips > At Transients
-
wait for window to appear
-
click ok
-
wait for menu to disappear
-
wait for Pro Tools to separate clip at transients
Find Number of Clips in Track
-
go to end of clips on selected track
-
select last clip in track
-
go to “rename” window to get file name
-
wait for window to appear
-
create string variable equal to selected clip's name
-
set variable to equal all text after the first "-" (it should be equal to the number of clips on this track, but this is a dangerous method and need to find a better solution
-
take string variable, change to int, divide it by the number of beats in time signature
-
set new variable (numberOfBars) to be equal to this value
-
click "OK" to close window
-
wait until window is closed
Number of Bars is Now Found
Restore Clips and return to session start
- select all clips in track
- heal clips
- set session time to 00:00:000
Setup data storage
Set data variables
-
set bar to equal 1
-
set time to equal Main Counter value
-
set tempo to Tempo value
-
create object array to store tempo data in
START LOOP
- create loop with length equal to numberOfBars variable
Identify Beats
- open Identify Beat window
- wait for window to appear
- write bar number in text box
- click OK to close window
- wait for window to disappear
Counter increase & Move to next Bar
-
count bar up by 1
-
navigate to next bar
Return to Beginning of Loop
END LOOP
Create .CSV FILE in Selected Directory with Title of Pro Tools Session (append “Beat Map”)
Format Object Array (beatMap) in a .csv friendly Manner
Write Object Array Contents to Newly Created File
Question
Hello!
I am not very fluent in coding, but I'm learning! I appreciate any feedback for making this code stronger.
I am uploading about 40 custom multitracks with nonstatic tempos to a cloud service that allows you to run and manage multitracks in a live performance from an iPad. (Similar to Ableton Live)
The app is called Playback by Multitracks.com
In order to be able to cue up specific song sections on demand in this app, I need to enter tempo changes, song sections and time signature changes via timecode (milliseconds).
The annoying part about this is that I can only enter the changes manually in their web browser, so doing this for 40 songs would take an insane amount of time.
I want to make a macro that will create a tempo map from an audio click track, capture all of the tempo change data, and then export that data as a .csv file.
Then I'll manually create markers for song sections and capture that data in a seperate macro and export it as another .csv file.
Finally. I'll create a final macro that will read the data, and then enter it into the website (hopefully I can find a way to do this!)
So my first roadblock I've run into is creating files with Soundflow.
Are there API Methods available for this? I saw a generateNewPath() method, but wasn't sure what it did.
Do I need to do this through AppleScript or the Command line?
If so, how do I correctly integrate my variables in Soundflow so that the scripts work?
I've included everything in my script up to my execAppleScript() method.
The "script:" portion is copied and pasted directly from Script Editor and works there, but not from Soundflow.
What I have now for this portion is a static version, but I want to make it more dynamic so that I could choose where I save the new file.
I know this is a lot, but I'd appreciate an help you all could give me!
Michael
Command Info
ID: user:cko6ddeuu0001bu10sdc9lkfu:cko6m4nhi0001ci10o4lni78o
Name: Beat Mapper
Source
//export tempo map from Pro Tools and save as a .csv file
//activate Pro Tools
sf.ui.proTools.appActivate();
//Select "CLICK" Track
sf.ui.proTools.trackGetByName({ name: "CLICK", makeVisible: true }).track.trackSelect();
//Starts process to find number of bars in the song
sf.ui.proTools.menuClick({
menuPath: ["Edit","Separate Clip","At Transients"],
});
//wait for menu to appear
sf.ui.proTools.windows.whoseTitle.is("Pre-Separate Amount").first.elementWaitFor({
waitType: "Appear",
});
//click ok
sf.ui.proTools.windows.whoseTitle.is("Pre-Separate Amount").first.buttons.whoseTitle.is("OK").first.elementClick();
//wait for menu to disappear
sf.ui.proTools.windows.whoseTitle.is("Pre-Separate Amount").first.elementWaitFor({
waitType: "Disappear",
});
//wait for Pro Tools to separate clip at transients (is there a better way to do this?)
sf.wait({
intervalMs: 1000,
});
//go to end of clips on selected track (what UI method could I use here?)
sf.keyboard.press({
keys: "alt+return",
});
sf.wait({
intervalMs: 10,
});
//select last clip in track
sf.keyboard.press({
keys: "shift+l",
});
sf.wait({
intervalMs: 10,
});
//go to rename window to get file name
sf.ui.proTools.menuClick({
menuPath: ["Clip","Rename..."],
});
//wait for window to appear
sf.ui.proTools.windows.whoseTitle.is("Name").first.elementWaitFor({
waitType: "Appear",
});
//create string variable equal to selected clip's name
var clipName = sf.ui.proTools.windows.whoseTitle.is("Name").first.groups.whoseTitle.is("Name").first.textFields.first.value.invalidate().value;
//remove all text prior to (and including) the first "-",
//then set variable to this value
//(it should be equal to the number of clips on this track,
//but this is a dangerous method and need to find a better soluction)
clipName = clipName.split("-").pop();
//*assumes song to be in 4/4 (need to build if/else statement that references time signature)
//finds number of bars in song
var numberofBars = parseInt(clipName)/4;
//click "OK" to close window
sf.ui.proTools.windows.whoseTitle.is("Name").first.buttons.whoseTitle.is("OK").first.elementClick();
//wait until window is closed
sf.ui.proTools.windows.whoseTitle.is("Name").first.elementWaitFor({
waitType: "Disappear",
});
//select all clips in track, heal clips, set session time to 00:00:000
//(need to find a UI method to accomplish these)
sf.keyboard.press({
keys: "cmd+a, cmd+h, return",
});
//Now I need to take the number of bars and use it for my loop
//The file I want to export will have these variables as headers
//should I set bar to a UI value instead of just assuming 1?
var bar = 1;
//takes value from Main Counter
//(I need to confirm that the Main Counter is set to "Min:Sec")
var time = sf.ui.proTools.mainWindow.groups.whoseTitle.is("Counter Display Cluster").first.textFields.whoseTitle.is("Main Counter").first.value.invalidate().value;
//do I need to make sure this view is opened in order to get this value?
var tempo = sf.ui.proTools.mainWindow.groups.whoseTitle.is("MIDI Control Cluster").first.textFields.whoseTitle.is("Tempo value").first.value.invalidate().value;
//create object array to store tempo data in
var beatMap = [ {
"bar" : bar,
"time" : time,
"tempo" : tempo
}
]
//sets loop to be equal to number of bars in song
for ( var i = 0; i < numberOfBars-1; i++) {
//open Identify Beat window
sf.ui.proTools.menuClick({
menuPath: ["Event","Identify Beat..."],
});
//wait for window to appear
sf.ui.proTools.children.whoseRole.is("AXWindow").first.elementWaitFor({
waitType: "Appear",
});
//write bar number in text box (is there a "set text in textbox" way to do this?)
sf.keyboard.type({
text: bar.toString(),
});
//click OK to close window
sf.ui.proTools.windows.first.buttons.whoseTitle.is("OK").first.elementClick();
//wait for window to disappear
sf.ui.proTools.windows.whoseTitle.is("Add Bar | Beat Marker").first.elementWaitFor({
waitType: "Disappear",
});
//count bar up by 1 (should I be using a UI element for this?)
bar = bar + 1;
//navigate to next bar (assumes song is in 4/4. need to create if else statement based on time signature)
//is there a UI feature for "tab to transient?"
sf.keyboard.press({
keys: "tab, tab, tab, tab",
});
//end loop
//once loop is completed, I want to create a .csv file and write my beatMap variable to the file
//I would need to either a) format my variable in a .csv compatible way, or b) do a conversion task before writing to the file.
//is there a system method for creating a new file, or do I need to use this?
//If I use AppleScript, how do I input my beatMap variable into the AppleScript?
//How can I make it so that I choose where I want to save my new file?
//should this be a separate macro?
sf.system.execAppleScript({
script: "set filePath to {\"Volumes:Extreme SSD:Downpour Band NY:MULTITRACKS STEM PREP:CSV Files:\" & \"text.csv\"} set theString to \ beatMap set theResult to writeTo(filePath, theString, text, false) if not theResult then display dialog \"There was an error writing the data!\" on writeTo(targetFile, theData, dataType, apendData) \t-- targetFile is the path to the file you want to write \t-- theData is the data you want in the file. \t-- dataType is the data type of theData and it can be text, list, record etc. \t-- apendData is true to append theData to the end of the current contents of the file or false to overwrite it \t \ttry \t\tset targetFile to targetFile as text \t\tset openFile to open for access file targetFile with write permission \t\tif apendData is false then set eof of openFile to 0 \t\twrite theData to openFile starting at eof as dataType \t\tclose access openFile \t\treturn true \ton error \t\ttry \t\t\tclose access file targetFile \t\tend try \t\treturn false \tend try",
path: "/Volumes/Extreme SSD/Downpour Band NY/MULTITRACKS STEM PREP/CSV Files",
});
}
Links
User UID: oafi4tHGD0Q1P16RSnW6BMRLqDI2
Feedback Key: sffeedback:oafi4tHGD0Q1P16RSnW6BMRLqDI2:-M_JI2aWEG-qRq3bnXjc
- Christian Scheuer @chrscheuer2021-05-10 05:24:52.843Z
Hi Michael.
Wow, this is very impressive! Thank you for posting such a clear summary of what could otherwise be very complex to understand :)
Just a quick first bit of help from my end – you can read & write files using
sf.file.
actions.You can use something like the following to turn your beatMap javascript array into a CSV file:
var beatMap = [{ "bar": bar, "time": time, "tempo": tempo }]; sf.file.writeText({ path: `~/Desktop/beatmap.csv`, text: 'Bar;Time;Tempo\n' + beatMap.map(e => `"${e.bar}";"${e.time}";"${e.tempo}"`).join('\n'), });