Make a class return something from a routine

I’m trying to write a class that loads soundfiles from a folder to buffers, converts the buffers to work with the PartConv Ugen and returns a dictionary with the prepared buffers.
This process takes some time, so i need to sync with the server, because of this i put the part where the class returns the dictionary inside a Routine. However i seem get an error because of that.
Here is my class:

LoadFilesTo {
	*ir { |path|
		{
			var irDict = ();
			PathName(path).files.do { |fp|
				var ir, irbuffer, bufsize, irspectrum, fftSize = 2048;
				irbuffer = Buffer.read(Server.default, fp.absolutePath);
				Server.default.sync;
				bufsize = PartConv.calcBufSize(fftSize, irbuffer);
				irspectrum = Buffer.alloc(Server.default, bufsize, 1);
				irspectrum.preparePartConv(irbuffer, fftSize);
				irDict[fp.fileNameWithoutExtension.asSymbol] = irspectrum;
			};
			Server.default.sync;
			^irDict
		}.fork
	}
}

The error reads

‘Routine-awake’ Out of context return of value: …

I tried to modify the class to prevent the error, like this:

LoadFilesTo {
*ir { |path|
	{
		var irDict = ();
		PathName(path).files.do { |fp|
			var ir, irbuffer, bufsize, irspectrum, fftSize = 2048;
			irbuffer = Buffer.read(Server.default, fp.absolutePath);
			Server.default.sync;
			bufsize = PartConv.calcBufSize(fftSize, irbuffer);
			irspectrum = Buffer.alloc(Server.default, bufsize, 1);
			irspectrum.preparePartConv(irbuffer, fftSize);
			irDict[fp.fileNameWithoutExtension.asSymbol] = irspectrum;
		};
		Server.default.sync;
		^irDict;
		nil
	}.fork
}

But then it won’t compile. Does someone have a solution?
I want to be able to use it like this:
a = LoadFilesTo.ir(“/soundfiles”)

you can specify an action to be performed after a buffer is read (can’t remember if it’s called action: or doneAction:). PartConv likewise…

here’s the function I use to prep a single ir which shows the action: key

(sorry about the formatting!)

Library.put(\functions,\prep,
{|in channel=0 fftsize=4096 | // returns bufnum
var irbuffer, bufsize, spectrum;
var path=PathName(in);
var bufnum = Buffer.new.bufnum;
irbuffer = Buffer.readChannel(s, in, channels:[channel], action: { |buffer|
bufsize = PartConv.calcBufSize( fftsize, buffer);
spectrum = Buffer.alloc(s, bufsize, 1, bufnum:bufnum);
spectrum.preparePartConv(buffer, fftsize);
spectrum.postln;
irbuffer.free;
});
bufnum;
});

if you iterate with a function like this you don’t need a routine or to use s.sync - the function will return the buffernumbers and the buffers will be filled asynchronously

The original error is because the method already returned once – it can’t return a second time.

That is, if your method goes method { fork { ... } }, then what happens is that the new thread is scheduled on some clock to be executed later, and then the method returns immediately. At that point, it’s too late – already gone – already lost the context – so any ^thing inside the fork has, literally, no idea where to return to (and the caller has already moved on).

semiquaver’s idea is a good one – and it’s the way the class library’s Buffer methods work. Your method should first create the array of (empty) Buffer objects, then initiate the actions to fill the buffers, then return the array.

The other alternative is to write the method as you have it, but leave out fork – the user is then required to do: fork { something.yourMethod } – if yourMethod is called from within a routine, then the calling routine will have to wait for it.

hjh

1 Like