Hello all!
I’m a student at the University of Washington, and I’m working with the DXARTS department on developing a Quark for reading and working with hrtf data using the SOFA format. We’ve decided that the simplest way to do this would be to interface with the existing SOFA matlab/octave library. This is all well and good - I’ve been able to achieve some decent results by writing temporary source code files, capturing the output by calling octave from the console using Pipe, and then parsing the output string into relevant data structures in SuperCollider. Here’s an example of what that looks like:
var runSOFAroutine;
~sofaSrcDir = /* path to sofa matlab/octave repo ... */ ;
/** Run octave code on a SOFA object using the SOFA API.
*
* \param hrtfPath the path name to the sofa file to load
* \param source the octave code to run
*
* The source code should refer to the SOFA object file as `hrtf`. Any output
* by the source code will be captured and returned as a string.
*/
runSOFAroutine = { | hrtfPath, source |
var sourceFile, octaveCmd;
var allSourceCode, pipe, output, lastLine, nextLine;
// the name of the octave command and the temporary source file
octaveCmd = "octave";
sourceFile = "temp.m";
// define the source code to run:
// first we'll need to bootstrap some things
allSourceCode = [
// add the path to the sofa M/O repo and initialize
"addpath('%');".format(~sofaSrcDir.fullPath),
"SOFAstart;",
// load the specified sofa file and mark the beginning of output
"hrtf = SOFAload('%');".format(hrtfPath.fullPath),
"printf('SuperCollider Data Interface\\n');",
// now we can append the specified source code
] ++ source;
// write the source code to the temporary file
File.use(sourceFile, "w", { | fp |
allSourceCode.do{ | line | fp.write(line ++ "\n"); };
});
// run the octave command, and pipe input into it
pipe = Pipe.argv([octaveCmd, sourceFile], "r");
// scan the output until coming across the output marker
lastLine = pipe.getLine; nextLine = pipe.getLine;
while ({ lastLine != "SuperCollider Data Interface" }, {
lastLine = nextLine; nextLine = pipe.getLine;
});
// write the rest of the output data into an output string
while ({ nextLine.notNil }, {
output = output ++ nextLine ++ "\n";
nextLine = pipe.getLine;
});
// clean up and return the output
pipe.close;
File.delete(sourceFile);
output
};
/** Get the source vector of a given index from a SOFA object.
*
* \param hrtfPath the path name to the sofa file to load
* \param index the index of the hrtf
* \param precision the decimal precision of the output
*
* \return a vector of measurements (azimuth, elevation, radius)
*/
~sourceVectorAt = { | hrtfPath, index, precision = 10 |
// compile the octave source code that computes the source vector
runSOFAroutine.(hrtfPath, [
// get a matrix of source vectors then grab the specified index
"apv = SOFAcalculateAPV(hrtf);",
"v = apv(%, :);".format(index),
// print each element (azi, ele, r) separated by commas
"printf('\\%.%f,\\%.%f,\\%.%f\\n', v(1), v(2), v(3));".format(
precision, precision, precision),
])
// split the output into list of values, collect as floats
.split($,).collect({ | val | val.asFloat })
};
However, this is maybe a bit sloppy, since I actually have to write a file to the disk in order to run it with octave. Ideally, I’d be able to open an octave interpreter using a Pipe, so that I can write to the pipe, as well as redirect it’s output - something that I had hoped would have looked like this:
var pipe = Pipe.new("octave", "rw");
pipe.write("printf('hello, world!\\n');");
pipe.getLine().postln;
pipe.close;
However, it seems that I can only give one of "r"
or "w"
to the pipe. Is there another way that I can redirect input and output from the console?
Thanks!
Josie
They/them/theirs