hi all-
my studio runs on a dante network, and i'm often switching dante controller between many sample rates throughout the day.
currently i have soundflow scripts for each sample rate in dante controller, but i'm trying to write a slicker script that automates these changes based on the pro tools or logic session i'm about to open.
here's the workflow i've come up with:
a) select the pro tools or logic session in finder.
b) copy the path name to clipboard and assign to a variable in soundflow.
c) remove session name from path and add "/Audio Files"... then use "Go to Folder" command in Finder to go the the session's Audio Files folder and select the first audio file.
d) get the sample rate of the audio file and assign to variable in soundflow.
e) use the sample rate to trigger my dante controller scripts.
f) open the originally selected session.
i've got everything working except D. (i actually have an apple script that can grab the sample rate for a selected file, but having trouble passing the selected audio file path name to it using the sf.system.execAppleScript command).
any ideas on how to get the sample rate of an audio file selected in finder to a soundflow variable? or some even nicer way to accomplish what i'm after?
- Raphael Sepulveda @raphaelsepulveda2023-01-17 19:55:28.648Z
Hey @JOSEPH_BRANCIFORTE ,
Here's one way to extract the sample rate off the first selected audio file in FInder:
/** @param {{ path: string }} arg */ function getSampleRateFromAudioFile({ path }) { /** @param {string} str */ function escapeCharacters(str) { // There are more characters that need to be escaped // these are the most common I know of return str.replace(/([ *])/g, "\\$1"); } const audioFileInfo = sf.system.exec({ commandLine: `afinfo ${escapeCharacters(path)}` }).result; const match = audioFileInfo.match(/(\d{5,6}) Hz/); if (!match) throw "Could not read sample rate from this file."; const sampleRate = match[1]; return sampleRate; } const sampleRate = getSampleRateFromAudioFile({ path: sf.ui.finder.firstSelectedPath }); log(sampleRate);
Chris Shaw @Chris_Shaw2023-01-17 20:58:18.473Z
you beat me to it.
here's how to do it via the finder:sf.ui.finder.appActivateMainWindow() // Once audio file is selected in Finder function getSrInfoField() { return sf.ui.finder.mainWindow.scrollAreas.first.invalidate() .children.whoseRole.is("AXStaticText").whoseValue.contains("kHz").first } //open info window on selected audio file sf.ui.finder.menuClick({ menuPath: ["File", "Get Info"] }) sf.wait({ intervalMs: 300 }) const moreInfoTriangle = sf.ui.finder.mainWindow.scrollAreas.first .children.whoseRole.is("AXDisclosureTriangle").allItems[1]; var srInfoField = getSrInfoField(); //Open Disclose triangle if necessary if (!srInfoField.exists) { moreInfoTriangle.elementClick(); sf.wait({ intervalMs: 200 }) } //Get SR info from Get Info window // - get sample rate incl "kHz" const srInfoValue = (getSrInfoField().value.invalidate().value) log(`SR Info: ${srInfoValue}`) //get sample rate number only const wavSampleRate = getSrInfoField().value.invalidate().value.split(" ")[0] log(`SR Value: ${wavSampleRate}`) // close info window sf.ui.finder.mainWindow.getElement("AXCloseButton")
- In reply toraphaelsepulveda⬆:
Nathan Salefski @nathansalefski
This is fantastic. Is there a way to modify this to return the sample rate as a shortened version. Example instead of returning 44100 it would return 44.1?
JOSEPH BRANCIFORTE @JOSEPH_BRANCIFORTE
you could just divide the value by 1000?
const wavSampleRateShort = Number(wavSampleRate)/1000;
Nathan Salefski @nathansalefski
That's a very good point lol
- In reply toJOSEPH_BRANCIFORTE⬆:JOSEPH BRANCIFORTE @JOSEPH_BRANCIFORTE
thanks @raphaelsepulveda & @Chris_Shaw... you guys are the best!
for whatever reason, raphael's script is not working for me ("Could not read sample rate from this file." every time), but chris's finder-based solution is!
although i imagine it is more robust to use the command line afinfo tool as in raphael's approach... so maybe we could try to troubleshoot that.
here is the finished script:
//// get the filepath & session type of selected session (currently supports pro tools & logic x) sf.keyboard.press({ keys: "cmd+option+c", }); var session_path = sf.clipboard.getText().text; var session_type = session_path.split('.').pop(); /// go to "audio files" folder at the same level as session. var audio_folder_path = (session_path.substring(0, session_path.lastIndexOf("/")).concat("/Audio Files")); sf.ui.finder.menuClick({ menuPath: ["Go", "Go to Folder…"], }); sf.wait({ intervalMs: 200, }); sf.keyboard.type({ text: audio_folder_path, }); sf.keyboard.press({ keys: "enter", }); sf.wait({ intervalMs: 1000, }); //// navigate to first file in "audio files" folder. sf.keyboard.press({ keys: "right", }); sf.wait({ intervalMs: 200, }); //////////////////// GET SAMPLE RATE OF AUDIO FILE //////////////// /// @Chris_Shaw's solution via Finder /// https://forum.soundflow.org/-9281/copy-the-sample-rate-of-selected-audio-file-in-finder-to-clipboard#post-3 // Once audio file is selected in Finder function getSrInfoField() { return sf.ui.finder.mainWindow.scrollAreas.first.invalidate() .children.whoseRole.is("AXStaticText").whoseValue.contains("kHz").first } //open info window on selected audio file sf.ui.finder.menuClick({ menuPath: ["File", "Get Info"] }) sf.wait({ intervalMs: 300 }) const moreInfoTriangle = sf.ui.finder.mainWindow.scrollAreas.first .children.whoseRole.is("AXDisclosureTriangle").allItems[1]; var srInfoField = getSrInfoField(); //Open Disclose triangle if necessary if (!srInfoField.exists) { moreInfoTriangle.elementClick(); sf.wait({ intervalMs: 200 }) } //Get SR info from Get Info window // - get sample rate incl "kHz" const srInfoValue = (getSrInfoField().value.invalidate().value) //get sample rate number only const SampleRate = getSrInfoField().value.invalidate().value.split(" ")[0] sf.ui.finder.menuClick({ menuPath: ["File", "Close Window"] }) ///////////////////////////////////////////////////////////////////// //// select dante controller preset from sample rate if (SampleRate == "96") { //Calling command "96k" from package "Joseph Branciforte" sf.soundflow.runCommand({ commandId: 'package:clbzhuwsg00056y10nuazkjls', props: {} }); } else if (SampleRate == "88.2") { //Calling command "88.2k" from package "Joseph Branciforte" sf.soundflow.runCommand({ commandId: 'package:clbzhurzw00046y10h3qg5u80', props: {} }); } else if (SampleRate == "48") { //Calling command "48k" from package "Joseph Branciforte" sf.soundflow.runCommand({ commandId: 'package:clbzhm0aj00036y108ffag9x9', props: {} }); } else if (SampleRate == "44.1") { //Calling command "44.1k" from package "Joseph Branciforte" sf.soundflow.runCommand({ commandId: 'package:clbzhko9e00026y10uhkb0dh6', props: {} }); } /////// wait a few seconds, then open the session (based on its filetype). sf.wait({ intervalMs: 4000, }); if (session_type === "ptx") { sf.file.open({ path: session_path, applicationPath: "/Applications/Pro Tools.app", }); } else if (session_type === "logicx") { sf.file.open({ path: session_path, applicationPath: "/Applications/Logic Pro X.app", }); }
Raphael Sepulveda @raphaelsepulveda2023-01-18 06:32:54.365Z
Glad that one of the approaches worked!
Let's see if we can troubleshoot the command line version.
Select an audio file in Finder and run the following script. What does it log?
/** @param {{ path: string }} arg */ function getAudioFileInfo({ path }) { /** @param {string} str */ function escapeCharacters(str) { // There are more characters that need to be escaped // these are the most common I know of return str.replace(/([ *])/g, "\\$1"); } const audioFileInfo = sf.system.exec({ commandLine: `afinfo ${escapeCharacters(path)}` }).result; return audioFileInfo; } const audioFileInfo = getAudioFileInfo({ path: sf.ui.finder.firstSelectedPath }); log(audioFileInfo);
JOSEPH BRANCIFORTE @JOSEPH_BRANCIFORTE
that appears to have worked as expected!
Raphael Sepulveda @raphaelsepulveda2023-01-18 16:48:34.823Z
Awesome!
If you run the first script I posted in this thread on that same audio file, it should log 96000.
If you run into some files that don’t work with the first script then send me the raw output like you just did so we can figure out what went wrong. Some characters need to be escaped before running them through command line, so it might be that the file contains one that I’m missing in the
escapeCharacters()
function.The cool thing about this approach is that it would eliminate having to automate finder if you pass it a path to one of the audio files directly to the
getSampleRateFromAudioFile()
instead of fetching it withsf.ui.finder.firstSelectedPath
. But we can get to that once you can get the first iteration working reliably.JOSEPH BRANCIFORTE @JOSEPH_BRANCIFORTE
it does!
looks like the # character is what was throwing off the other attempt, which turns out to be pretty common in audio file names.
what's the best way to include # in the escaped characters list? sorry for my elementary javascript questions... i'm learning!
Raphael Sepulveda @raphaelsepulveda2023-01-18 22:49:53.623Z
Ah ha!
Modify the
escapeCharacters()
function as follows. Take a look where I added # among a few others in between the square brackets. You can keep adding characters in these as you encounter them!function escapeCharacters(str) { return str.replace(/([ \\&*()#])/g, "\\$1"); // Add characters to be escaped in between the brackets of the RegEx pattern }
Raphael Sepulveda @raphaelsepulveda2023-01-18 23:30:41.914Z
Once you have that going, you could do something like this to tie it all together:
/** @param {{ path: string }} arg */ function getSampleRateFromAudioFile({ path }) { /** @param {string} str */ function escapeCharacters(str) { // There are more characters that need to be escaped // these are the most common I know of return str.replace(/([ \\&*()#])/g, "\\$1"); // Add characters to be escaped in between the brackets of the RegEx pattern } const audioFileInfo = sf.system.exec({ commandLine: `afinfo ${escapeCharacters(path)}` }).result; const match = audioFileInfo.match(/(\d{5,6}) Hz/); if (!match) throw "Could not read sample rate from this file."; const sampleRate = match[1]; return sampleRate; } function selectDanteControllerSampleRatePreset({ sampleRate }) { if (sampleRate == "96000") { //Calling command "96k" from package "Joseph Branciforte" sf.soundflow.runCommand({ commandId: 'package:clbzhuwsg00056y10nuazkjls', props: {} }); } if (sampleRate == "88200") { //Calling command "88.2k" from package "Joseph Branciforte" sf.soundflow.runCommand({ commandId: 'package:clbzhurzw00046y10h3qg5u80', props: {} }); } if (sampleRate == "48000") { //Calling command "48k" from package "Joseph Branciforte" sf.soundflow.runCommand({ commandId: 'package:clbzhm0aj00036y108ffag9x9', props: {} }); } if (sampleRate == "44100") { //Calling command "44.1k" from package "Joseph Branciforte" sf.soundflow.runCommand({ commandId: 'package:clbzhko9e00026y10uhkb0dh6', props: {} }); } // wait a few seconds sf.wait({ intervalMs: 4000, }); } /** @param {{ path: string, fileExtension: string }} args */ function openSession({ path, fileExtension }) { const daw = { "ptx": "Pro Tools", "logicx": "Logic Pro X" }; sf.file.open({ path, applicationPath: `/Applications/${daw[fileExtension]}.app`, }); } function main() { // get the filepath & session type of selected session (currently supports pro tools & logic x) const session_path = sf.ui.finder.firstSelectedPath; const session_type = session_path.split('.').pop(); // Get "Audio Files" folder at the same level as session. const audio_folder_path = session_path.substring(0, session_path.lastIndexOf("/")).concat("/Audio Files"); const firstFilePathInAudioFolder = sf.file.directoryGetFiles({ path: audio_folder_path, searchPattern: "*.wav" // Change to desired audio format }).paths[0]; selectDanteControllerSampleRatePreset({ sampleRate: getSampleRateFromAudioFile({ path: firstFilePathInAudioFolder }), }); openSession({ path: session_path, fileExtension: session_type }); } main();
JOSEPH BRANCIFORTE @JOSEPH_BRANCIFORTE
thanks for the explanation on the regex... that did the trick!
this new updated script is working perfectly for me.
it's really satisfying to be able to open a session and know that my entire studio is automatically at the right sample rate, just one less thing to think about / do when i have a client looking over my shoulder ready to get to work!
JOSEPH BRANCIFORTE @JOSEPH_BRANCIFORTE
i still wish there was an "afinfo" equivalent for the ptx / logix sessions to be able to derive the sample rate directly from the session file. but i think looking in the associated audio files folder is a pretty decent workaround!
Raphael Sepulveda @raphaelsepulveda2023-01-19 01:09:03.433Z
I know, but your workaround is pretty ingenious!