Reading multiple files into buffers - Buffer.read error

Hi!

I am using Eli Fieldsteel example code to load a sample library into Buffers. The sample library are packs from FreeSound, each pack has it folder name which will be loaded as a dictionary key. However, while some packs are being loaded properly, some do not. Here is the code:

(
s.options.memSize_(2.pow(20));
s.options.numBuffers_(4096); // the amount of sound files is ~1600
)

~path = PathName(thisProcess.nowExecutingPath).parentPath++"buffers/";

//3. define piece-specific functions
(
~makeBuffers = {
	b = Dictionary.new;
	PathName(~path).entries.do{
		arg subfolder;
		b.add(
			subfolder.folderName.asSymbol ->
			Array.fill(
				subfolder.entries.size,
				{
					arg i;
					postf("reading file % in folder '%' \n", i, subfolder.folderName);
					Buffer.read(s, subfolder.entries[i].fullPath);
					//Buffer.readChannel(s, subfolder.entries[i].fullPath, channels: [0]);
				}
			)
		);
	};
};
)

~makeBuffers.value();




this works:

b[\boneFall][1].play;
b[\boneFall][1];
-> Buffer(26, 35832, 1, 44100.0, C:\Users\path_to_the_file\filename.wav)

this does not:

b[\orchestra][1].play;
b[\orchestra][1];
-> Buffer(1280, nil, nil, nil, C:\Users\path_to_the_file\filename.wav)

I am getting the following error message when trying to play the b[\orchestra][1].play; above:

ERROR: a PlayBuf: wrong number of channels (nil)

PROTECTED CALL STACK:
	MultiOutUGen:initOutputs	0000016CD7AA6540
		arg this = a PlayBuf
		arg numChannels = nil
		arg rate = audio
	a FunctionDef	0000016CD5697A80
		sourceCode = "<an open Function>"
		var player = nil
	SynthDef:buildUgenGraph	0000016CD8B03900
		arg this = SynthDef:temp__11
		arg func = a Function
		arg rates = nil
		arg prependArgs = [  ]
		var result = nil
		var saveControlNames = [ ControlName  P 0 i_out scalar 0 ]
	a FunctionDef	0000016CD5765FC0
		sourceCode = "<an open Function>"
		arg i_out = an OutputProxy
		var result = nil
		var rate = nil
		var env = nil
	SynthDef:buildUgenGraph	0000016CD8B03900
		arg this = SynthDef:temp__11
		arg func = a Function
		arg rates = nil
		arg prependArgs = [  ]
		var result = nil
		var saveControlNames = nil
	a FunctionDef	0000016CD8B01E80
		sourceCode = "<an open Function>"
	Function:prTry	0000016CD7464CC0
		arg this = a Function
		var result = nil
		var thread = a Thread
		var next = nil
		var wasInProtectedFunc = false
	
CALL STACK:
	Exception:reportError
		arg this = <instance of Error>
	Nil:handleError
		arg this = nil
		arg error = <instance of Error>
	Thread:handleError
		arg this = <instance of Thread>
		arg error = <instance of Error>
	Object:throw
		arg this = <instance of Error>
	Function:protect
		arg this = <instance of Function>
		arg handler = <instance of Function>
		var result = <instance of Error>
	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 = "b[\orchestra][10].play"
		var doc = nil
		var ideClass = <instance of Meta_ScIDE>
	Process:interpretPrintCmdLine
		arg this = <instance of Main>
^^ The preceding error dump is for ERROR: a PlayBuf: wrong number of channels (nil)

I’ve tried also Buffer.readChannels to load all of them as mono, but with no good result… How to solve? Is it something related to the assyncronous way that Buffer.read operates?

Looks like a sync issue.
Try this?

s.waitForBoot {
   ~makeBuffers.value();
   Server.default.sync;
   b[\orchestra][1].play;
}

By the way, this is probably not the best function to use.
Try this:

~getBuffers = { |path|
	var unpackFolderOrFile = { |folderOrFile|
		if( folderOrFile.isFile, 
			{ Buffer.read(s, folderOrFile.fullPath)	},
			{ folderOrFile.entries.collect({|e| unpackFolderOrFile.(e) }) }
		)
	};
	
	PathName(path).entries.collect({ |subfolder|
		subfolder.folderName.asSymbol -> unpackFolderOrFile.(subfolder)
	}).asEvent
};

This way it won’t override whatever you have assigned to b and you can pass in the path, using it multiple times. It also returns the data, and allows nested folders, although only keys are assigned to the top level folder.
Use it like this:

~xylo_path = "/...";
s.waitForBoot {
	~xylo_bufs = ~getBuffers.(~xylo_path);
	s.sync;
	~xylo_bufs['Soft Mallets'][0].play
}

Have you increased the server’s options object’s numBuffers? Help file says the default is 1024, but you’re trying to load over 1200. You’ll have to raise the limit.

hjh

Thanks a lot! Very elegant recursive solution!

Only to clarify a bit further: the problem is the Array.fill?

I don’t see where the overriding is occuring… Is the Dictionary’s b.add an override operation? If yes, I thought that it was only filling the each key once with a single call of Array.fill per key.

This is not working, so can I say that synchronization is not the issue then?

About this:

Yes, I’ve did it

but the only thing that solved the problem was the recursive search function.

This b does not exist inside the function, but globally. This overrides what ever the value was before.

In general, all the information that you need should be passed to a function through an argument, and all results should be returned through the function’s return. There are exceptions to this, but if you follow those rules it becomes easier to think about code.

But this wouldn’t be why it wasn’t working. If the code I posted works but the original doesn’t then I am unsure.

Nice piece of Code, thanks for sharing and sorry, for reopening the thread, I just wanted to go deeper into the code.
How would you go for avoiding no audio files to populate the event? Let’s say you have some folders in which there are text files or other metadata (reaper, Ableton).
Playing with your code, I came up with this (remove it from the event by looking for buffers), but there might be a more elegant way to do it.

(
~getBuffers = { |path|
	var count = 0;
	var unpackFolderOrFile = { |folderOrFile, i|
		if(folderOrFile.isFolder == false )
		{
			if( i != nil) {count = count + 1}{count = count + 1};
			if(folderOrFile.isFile && folderOrFile.extension == "wav")
			{Buffer.read(s, folderOrFile.fullPath, action:{|b|b.postln}, bufnum:count);}
			{count = (count - 1)}
		}
		{ folderOrFile.entries.collect({|e| unpackFolderOrFile.(e)})}
	};
	
	PathName(path).entries.collect({ |subfolder, i|
		subfolder.folderName.asSymbol -> unpackFolderOrFile.(subfolder, i);
	}).asEvent
};
);
(
~xylo_path = "/somefolder/somefolder/somefolder/someFolderWithSoundsAndOtherStuff/";
s.waitForBoot {
	~xylo_bufs = ~getBuffers.(~xylo_path);
	s.sync;
	~xylo_bufs.keys.collect{ |i| ~xylo_bufs[i].do{ |k| if(k.class != Buffer){~xylo_bufs[i].remove(k)}}};
}
);

Here is how I’d do that.

~getBuffers = { |path, fileTypes|
	var isFolder = { |f|
		f.entries
		.collect(unpackFolderOrFile)
		.reject(_.isNil)
	};
	var isFile = { |f| 
		if(fileTypes.includes( f.extension.asSymbol ),
			{ Buffer.read(s, f.fullPath) },
			nil
		)
	};
	var unpackFolderOrFile = { |folderOrFile|
		if(folderOrFile.isFolder, 
			{isFolder.(folderOrFile)}, 
			{isFile.(folderOrFile)}
		)	
	};
	
	PathName(path).entries.collect({ |subfolder, i|
		subfolder.folderName.asSymbol -> unpackFolderOrFile.(subfolder);
	}).asEvent
};

s.waitForBoot {
	var path = "....";
	~b = ~getBuffers.(path, [\wav]);
}

There are many ways you could do this, but I think a list of acceptable file types is a good way to go. All you have to do is check the files types match before loading the buffer then use reject to get rid of the non buffers.

1 Like

Thanks. .reject( allows for an elegant solution.