Loading audio file stored locally into remote buffer?

Hi, I’m creating a composition that involves 8 computers. One leader computer (macmini), and 7 follower computer’s (raspberry pi 4’s). In a simplified way: the leader computer records an audio file, and the 7 follower computer’s need to play back that audio file.

My question is: is it possible for a local machine to create an audio buffer on a remote server, and load into the remote buffer an audio file stored on the local machine? I have tried that, and failed. Incidentally, I also tried to create a buffer on a remote machine & load into it an audio file stored on the same remote machine, and this failed too.

However creating and playing SinOsc on my remote machine works fine. This is my attempt:

// connect local machine to remote server (this works!)
(
o = ServerOptions.new;
o.maxLogins = 2;
t = Server.remote(\remote, NetAddr("192.168.1.201", 57110), o);
)

// play SinOsc on remote server t (this works!)
{ SinOsc.ar(200, 0, 0.5) }.play(t);
// freeAll on remote server (this works!)
t.freeAll

// allocate buffer on remote server (seems to work?)
// reports: -> Buffer(14, 4800000.0, 1, 48000.0, nil)
d = Buffer.alloc(t, 100 * t.sampleRate);
// Attempt #1: load audio file into buffer from local machine path (seems to work?)
// reports: -> Buffer(15, nil, nil, nil, /Users/pvh/src/Horns/supercollider/audio/VOICE_COMP.wav)
d = Buffer.read(t, "/Users/pvh/src/Horns/supercollider/audio/VOICE_COMP.wav");
// Attempt #2: load audio file into buffer from remote machine path (seems to work?)
// reports: -> Buffer(18, nil, nil, nil, /home/pi/src/Horns/supercollider/audio/VOICE_COMP.wav)
d = Buffer.read(t, "/home/pi/src/Horns/supercollider/audio/VOICE_COMP.wav");
// play back the buffer on remote machine: this fails!!
d.play(t);

Below are the error messages I receive for “Attempt #1”, which tries to play back a remote buffer which has had an audio file loaded from the local machine:

ERROR: Message 'binaryValue' not understood.
RECEIVER:
Instance of Server {    (0x7ff24b931ec8, gc=58, fmt=00, flg=00, set=05)
  instance variables [30]
    name : Symbol 'remote'
    addr : instance of NetAddr (0x7ff24b8763e8, size=4, set=2)
    clientID : Integer 0
    isLocal : false
    inProcess : false
    sendQuit : nil
    remoteControlled : true
    maxNumClients : Integer 2
    options : instance of ServerOptions (0x7ff2470448b8, size=40, set=6)
    latency : Float 0.200000   9999999A 3FC99999
    dumpMode : Integer 0
    nodeAllocator : instance of NodeIDAllocator (0x7ff25ac34648, size=7, set=3)
    controlBusAllocator : instance of ContiguousBlockAllocator (0x7ff2469b1ff8, size=6, set=3)
    audioBusAllocator : instance of ContiguousBlockAllocator (0x7ff25f313358, size=6, set=3)
    bufferAllocator : instance of ContiguousBlockAllocator (0x7ff24655b908, size=6, set=3)
    scopeBufferAllocator : nil
    tree : nil
    defaultGroup : instance of Group (0x7ff246546658, size=5, set=3)
    defaultGroups : instance of Array (0x7ff24b819f18, size=2, set=2)
    syncThread : nil
    syncTasks : nil
    window : instance of QWindow (0x7ff25f210028, size=5, set=3)
    scopeWindow : nil
    emacsbuf : nil
    volume : instance of Volume (0x7ff247613f18, size=15, set=4)
    recorder : instance of Recorder (0x7ff246dac8b8, size=14, set=4)
    statusWatcher : instance of ServerStatusWatcher (0x7ff24b931c98, size=20, set=5)
    pid : nil
    serverInterface : nil
    pidReleaseCondition : instance of Condition (0x7ff24b8714f8, size=2, set=2)
}
ARGS:
PATH: /Users/pvh/src/Horns/supercollider/hornmac_controller.scd

PROTECTED CALL STACK:
	Meta_MethodError:new	0x7ff25f7d5980
		arg this = DoesNotUnderstandError
		arg what = nil
		arg receiver = remote
	Meta_DoesNotUnderstandError:new	0x7ff25f7d7cc0
		arg this = DoesNotUnderstandError
		arg receiver = remote
		arg selector = binaryValue
		arg args = [  ]
	Object:doesNotUnderstand	0x7ff25f0b4f80
		arg this = remote
		arg selector = binaryValue
		arg args = nil
	a FunctionDef	0x7ff25f3b02c0
		sourceCode = "<an open Function>"
		var player = nil
	SynthDef:buildUgenGraph	0x7ff260c56580
		arg this = a SynthDef
		arg func = a Function
		arg rates = nil
		arg prependArgs = [  ]
		var result = nil
		var saveControlNames = [ ControlName  P 0 i_out scalar 0 ]
	a FunctionDef	0x7ff2604e64c0
		sourceCode = "<an open Function>"
		arg i_out = an OutputProxy
		var result = nil
		var rate = nil
		var env = nil
	SynthDef:buildUgenGraph	0x7ff260c56580
		arg this = a SynthDef
		arg func = a Function
		arg rates = nil
		arg prependArgs = [  ]
		var result = nil
		var saveControlNames = nil
	a FunctionDef	0x7ff260c54bc0
		sourceCode = "<an open Function>"
	Function:prTry	0x7ff25fa73100
		arg this = a Function
		var result = nil
		var thread = a Thread
		var next = nil
		var wasInProtectedFunc = false
	
CALL STACK:
	DoesNotUnderstandError:reportError
		arg this = <instance of DoesNotUnderstandError>
	Nil:handleError
		arg this = nil
		arg error = <instance of DoesNotUnderstandError>
	Thread:handleError
		arg this = <instance of Thread>
		arg error = <instance of DoesNotUnderstandError>
	Object:throw
		arg this = <instance of DoesNotUnderstandError>
	Function:protect
		arg this = <instance of Function>
		arg handler = <instance of Function>
		var result = <instance of DoesNotUnderstandError>
	SynthDef:build
		arg this = <instance of SynthDef>
		arg ugenGraphFunc = <instance of Function>
		arg rates = nil
		arg prependArgs = nil
	Function:play
		arg this = <instance of Function>
		arg target = <instance of Group>
		arg outbus = 0
		arg fadeTime = 0.02
		arg addAction = 'addToHead'
		arg args = nil
		var def = nil
		var synth = nil
		var server = <instance of Server>
		var bytes = nil
		var synthMsg = nil
	Interpreter:interpretPrintCmdLine
		arg this = <instance of Interpreter>
		var res = nil
		var func = <instance of Function>
		var code = "d.play(t);"
		var doc = nil
		var ideClass = <instance of Meta_ScIDE>
	Process:interpretPrintCmdLine
		arg this = <instance of Main>
^^ ERROR: Message 'binaryValue' not understood.
RECEIVER: remote

Also, here are the error messages I receive for “Attempt #2”, which tries to play back a remote buffer which has had an audio file loaded from the same remote machine:

ERROR: Message 'binaryValue' not understood.
RECEIVER:
Instance of Server {    (0x7ff24b931ec8, gc=58, fmt=00, flg=00, set=05)
  instance variables [30]
    name : Symbol 'remote'
    addr : instance of NetAddr (0x7ff24b8763e8, size=4, set=2)
    clientID : Integer 0
    isLocal : false
    inProcess : false
    sendQuit : nil
    remoteControlled : true
    maxNumClients : Integer 2
    options : instance of ServerOptions (0x7ff2470448b8, size=40, set=6)
    latency : Float 0.200000   9999999A 3FC99999
    dumpMode : Integer 0
    nodeAllocator : instance of NodeIDAllocator (0x7ff25ac34648, size=7, set=3)
    controlBusAllocator : instance of ContiguousBlockAllocator (0x7ff2469b1ff8, size=6, set=3)
    audioBusAllocator : instance of ContiguousBlockAllocator (0x7ff25f313358, size=6, set=3)
    bufferAllocator : instance of ContiguousBlockAllocator (0x7ff24655b908, size=6, set=3)
    scopeBufferAllocator : nil
    tree : nil
    defaultGroup : instance of Group (0x7ff246546658, size=5, set=3)
    defaultGroups : instance of Array (0x7ff24b819f18, size=2, set=2)
    syncThread : nil
    syncTasks : nil
    window : instance of QWindow (0x7ff25f210028, size=5, set=3)
    scopeWindow : nil
    emacsbuf : nil
    volume : instance of Volume (0x7ff247613f18, size=15, set=4)
    recorder : instance of Recorder (0x7ff246dac8b8, size=14, set=4)
    statusWatcher : instance of ServerStatusWatcher (0x7ff24b931c98, size=20, set=5)
    pid : nil
    serverInterface : nil
    pidReleaseCondition : instance of Condition (0x7ff24b8714f8, size=2, set=2)
}
ARGS:
PATH: /Users/pvh/src/Horns/supercollider/hornmac_controller.scd

PROTECTED CALL STACK:
	Meta_MethodError:new	0x7ff25f7d5980
		arg this = DoesNotUnderstandError
		arg what = nil
		arg receiver = remote
	Meta_DoesNotUnderstandError:new	0x7ff25f7d7cc0
		arg this = DoesNotUnderstandError
		arg receiver = remote
		arg selector = binaryValue
		arg args = [  ]
	Object:doesNotUnderstand	0x7ff25f0b4f80
		arg this = remote
		arg selector = binaryValue
		arg args = nil
	a FunctionDef	0x7ff25f3b02c0
		sourceCode = "<an open Function>"
		var player = nil
	SynthDef:buildUgenGraph	0x7ff260c56580
		arg this = a SynthDef
		arg func = a Function
		arg rates = nil
		arg prependArgs = [  ]
		var result = nil
		var saveControlNames = [ ControlName  P 0 i_out scalar 0 ]
	a FunctionDef	0x7ff2604e64c0
		sourceCode = "<an open Function>"
		arg i_out = an OutputProxy
		var result = nil
		var rate = nil
		var env = nil
	SynthDef:buildUgenGraph	0x7ff260c56580
		arg this = a SynthDef
		arg func = a Function
		arg rates = nil
		arg prependArgs = [  ]
		var result = nil
		var saveControlNames = nil
	a FunctionDef	0x7ff260c54bc0
		sourceCode = "<an open Function>"
	Function:prTry	0x7ff25fa73100
		arg this = a Function
		var result = nil
		var thread = a Thread
		var next = nil
		var wasInProtectedFunc = false
	
CALL STACK:
	DoesNotUnderstandError:reportError
		arg this = <instance of DoesNotUnderstandError>
	Nil:handleError
		arg this = nil
		arg error = <instance of DoesNotUnderstandError>
	Thread:handleError
		arg this = <instance of Thread>
		arg error = <instance of DoesNotUnderstandError>
	Object:throw
		arg this = <instance of DoesNotUnderstandError>
	Function:protect
		arg this = <instance of Function>
		arg handler = <instance of Function>
		var result = <instance of DoesNotUnderstandError>
	SynthDef:build
		arg this = <instance of SynthDef>
		arg ugenGraphFunc = <instance of Function>
		arg rates = nil
		arg prependArgs = nil
	Function:play
		arg this = <instance of Function>
		arg target = <instance of Group>
		arg outbus = 0
		arg fadeTime = 0.02
		arg addAction = 'addToHead'
		arg args = nil
		var def = nil
		var synth = nil
		var server = <instance of Server>
		var bytes = nil
		var synthMsg = nil
	Interpreter:interpretPrintCmdLine
		arg this = <instance of Interpreter>
		var res = nil
		var func = <instance of Function>
		var code = "d.play(t);"
		var doc = nil
		var ideClass = <instance of Meta_ScIDE>
	Process:interpretPrintCmdLine
		arg this = <instance of Main>
^^ ERROR: Message 'binaryValue' not understood.
RECEIVER: remote

Thank you very much for any suggestions:)

I don’t have a remote machine to try this out on, but you should be able to load a collection (or maybe use sendCollection if this doesn’t work):

f = SoundFile.openRead(Platform.resourceDir +/+ "sounds/a11wlk01.wav");
f.sampleFormat;

// To get data, create a FloatArray or Signal first
d = FloatArray.newClear(f.numFrames);
f.readData(d);

b = Buffer.loadCollection(s, d, action:{"done".postln});
x = { PlayBuf.ar(1, b, BufRateScale.kr(b), loop: 0) * 0.5 }.play;

f.close

I believe the one @petervh wants here is sendCollection (usable on either a Buffer or a Collection) - this streams the data over the network to a remote server.

Be wary: sending the data is not instantaneous, so you’ll want to use the action argument to pass a function that will get executed when the buffer is fully sent - anything synths that or other server processes that require the buffer should go in here.

The server is on machine B, and the file is on machine A.

If you want server B to read a file that exists only on machine A, then there are only two options:

  • Send the data over the network, as scztt suggests. This is slow.
  • Or, set up remote filesystem access so that B can read files from A using a specially-formatted path. This will be fast over a LAN, but perhaps tricky to set up (and the specific method will depend on the OS).

The latter isn’t something I’ve done a lot myself so I don’t have more specific advice, but if you can get it so that machine B can cat a file from machine A at the command line, then buffer reading “should” work too. There should be many pages online explaining how to configure remote filesystem access.

loadCollection uses the file system, and you don’t have control over the path, so it won’t work remotely.

hjh

Thank you @Sam_Pluta, @scztt, & @jamshark70! I am glad to know that this is possible.

Actually in the particular scenario of my installation, I have at least 20 seconds between when the audio file finishes recording on the leader computer, and when it needs to start playing back on the follower computers. So I think I should be okay using sendCollection. But a shared directory is doable too & a creative solution.

I am currently at a distance from the networked computer setup for the next ~2 weeks, so I will report back later on my progress. I’m planning to send patterns and a synthdef with the buffer out of the leader and into the followers, and have the 7 followers play together in time. So I might need to deal with some kind of synchronization routine, which may introduce new questions. I will update, and thanks again to all !