Problem with boolean .isFile and .isFolder on windows?

I’ve created this code that loads a bunch of samples/buffers from your samples folder into a Dictionary. This borrows heavily from what I’ve seen Eli Fieldsteel do in his streams.
What I modified was being able to also load samples from nested folders within the subfolders.

s.waitForBoot({

  var subfolderstemp, subfolders1, subfolders2,path, dictionary;

		d = Dictionary.new;
		path = PathName(thisProcess.nowExecutingPath).parentPath;
		subfolderstemp = PathName(path ++ "mainsamplefolder/").folders;
      subfolders1= [];
      subfolderstemp.do{arg n, i;
	   case { n.entries[i].isFile} {subfolders1=subfolders1.add(n)}
      };
      subfolders2=[];
      subfolderstemp.do{arg n, i;
	   case { n.entries[i].isFolder} {subfolders2=subfolders2.add(n)};
      };
      subfolders2= subfolders2.collect{arg n,i; n.folders}.flat;
      subfolders1.do{
	arg dir;
	d.add(
		dir.folderName.asSymbol ->
		Array.fill(
			dir.entries.size,
			{
				arg idx;
				Buffer.read(s, dir.entries[idx].fullPath);
			}
		)
	);
};

subfolders2.do{
	arg dir;
	d.add(
		dir.folderName.asSymbol ->
		Array.fill(
			dir.entries.size,
			{
				arg idx;
				Buffer.read(s, dir.entries[idx].fullPath);
			}
		)
	);
};
});

//test:
d[\nested][0].play
d[\subfoldersound][0].play
  1. the .scd document must be saved in a folder (projectfolder) together with the samples folder

ProjectFolder

  • document.scd
  • samplefolder

→ subfolder1 that contains soundfiles

→ subfolder2

 -nestedfolder1 that contains soundfiles

 -nestedfolder2 that contains soundfiles

I’ve got it to work a day or two but now it doesn’t really work and I am not sure if it is something with my windows system. I can no longer get it to respond to isFile and isFolder.

Maybe there is an altogether (more) clever way of loading nested folders but am curious of people have similar problems with isFile and isFolder (on a windows system)

Rule of thumb for support forums: it is not useful to say “it doesn’t work.”

It is useful to say “I got this error message” and quote the error text, or if it’s unexpected behavior but no error, to say what you expected and what it actually did.

Without describing the symptom fully and accurately, other readers will either not be able to respond, or guess incorrectly and give you a wrong answer.

hjh

1 Like

FWIW, I looked at the code for these methods, and I can’t see why it wouldn’t work.

I don’t have access to SC in Windows at the moment, so I have zero idea what is the issue you’re seeing. Can’t move forward until you can provide the error text (with stack trace).

This really should be done with recursion. It’s the cleanest way to go into any number of nested sub-sub-sub-sub-folders. (Your way might handle subfolders, but what if they are 8 levels deep?)

(
~scanDirectory = { |path, dict|
	var d;
	if(dict.isNil) {
		dict = Dictionary.new;
	};
	d = Dictionary.new;
	// these will be mixed directories or files
	(path +/+ "*").pathMatch.do { |subpath|
		switch(File.type(subpath))
		{ \regular } {
			// all recursion needs a non-recursive, exit branch
			// a normal file is the non-recursive branch
			~handleOneFile.(subpath, d)
		}
		{ \directory } {
			// a directory is the recursive branch
			~scanDirectory.(subpath, d);
		}
		// maybe need to handle symlinks too,
		// but I don't have time right now
	};
	// did we find anything?
	if(d.notEmpty) {
		dict.put(path.basename, d);
	};
	dict
};

~handleOneFile = { |path, dict|
	if(path.splitext[1] == "mp3") {
		dict.put(path.basename, "Buffer goes here I guess");
	};
	dict
};
)

Note: I haven’t checked carefully to see if this is producing exactly the same Dictionary format as your example. You are responsible for that. This is just to demonstrate how the directory function can call itself to handle subdirectories.

hjh

Thanks! Here is the complete error message. Not sure what stack trace is and tried to look it up.

ERROR: Message 'isFile' not understood.
RECEIVER:
   nil
ARGS:
PATH: C:/Users/nilsw/Desktop/SC-del/mydocument.scd

PROTECTED CALL STACK:
	Meta_MethodError:new	000001D8DAE6B4C0
		arg this = DoesNotUnderstandError
		arg what = nil
		arg receiver = nil
	Meta_DoesNotUnderstandError:new	000001D8DAE6D780
		arg this = DoesNotUnderstandError
		arg receiver = nil
		arg selector = isFile
		arg args = [  ]
	Object:doesNotUnderstand	000001D8DA9A2840
		arg this = nil
		arg selector = isFile
		arg args = nil
	a FunctionDef	000001D8DF4B9048
		sourceCode = "<an open Function>"
		arg n = PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\brass\)
		arg i = 2
	ArrayedCollection:do	000001D8DDCE7A80
		arg this = [ PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\analog_testtone\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\artpepper\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\brass\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\breaks\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\cheetahMD16\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\chords\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\conga\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\dubstep\), PathName(C:\Us...etc...
		arg function = a Function
		var i = 2
	a FunctionDef	000001D8DEB31748
		sourceCode = "{

  var subfolderstemp, subettfolders, subtvafolders,path, dictionary;

		d = Dictionary.new;
		path = PathName(thisProcess.nowExecutingPath).parentPath;
		subfolderstemp = PathName(path ++ \"buflibrary/\").folders; 
      subettfolders= [];
      subfolderstemp.do{arg n, i;
	   case { n.entries[i].isFile} {subettfolders=subettfolders.add(n)}
      };
      subtvafolders=[];
      subfolderstemp.do{arg n, i;
	   case { n.entries[i].isFolder} {subtvafolders=subt...etc..."
		var subfolderstemp = [ PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\analog_testtone\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\artpepper\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\brass\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\breaks\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\cheetahMD16\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\chords\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\conga\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\dubstep\), PathName(C:\Us...etc...
		var subettfolders = [ PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\analog_testtone\), PathName(C:\Users\nilsw\Desktop\SC-del\buflibrary\artpepper\) ]
		var subtvafolders = nil
		var path = C:/Users/nilsw/Desktop/SC-del/
		var dictionary = nil
	Routine:prStart	000001D8DD946FC0
		arg this = a Routine
		arg inval = 311.2369753

CALL STACK:
	DoesNotUnderstandError:reportError
		arg this = <instance of DoesNotUnderstandError>
	< closed FunctionDef >
		arg error = <instance of DoesNotUnderstandError>
	Integer:forBy
		arg this = 0
		arg endval = 2
		arg stepval = 2
		arg function = <instance of Function>
		var i = 2
		var j = 1
	SequenceableCollection:pairsDo
		arg this = [*4]
		arg function = <instance of Function>
	Scheduler:seconds_
		arg this = <instance of Scheduler>
		arg newSeconds = 318.223661
	Meta_AppClock:tick
		arg this = <instance of Meta_AppClock>
		var saveClock = <instance of Meta_SystemClock>
	Process:tick
		arg this = <instance of Main>
^^ ERROR: Message 'isFile' not understood.
RECEIVER: nil

I would guess it’s here:

You’re iterating over one collection (subfolderstemp) but using the indices to access another collection (n.entries[i]). This is fine as long as the “entries” collection is at least as large as the subfolderstemp collection. If entries is smaller, then i will be out of range for this collection and you’ll get nil.

The pattern you’re using is safe if you have controlled for both collections being the same size. But the collections are coming from different sources, so you have no guarantee that the sizes will work out that way. It accidentally worked in your first test but you never had any assurance that it would always work.

I’d really suggest restructuring the iteration. Tbh, reading your code, it doesn’t make sense to me. If you want to traverse subfolders, recursion is the way.

hjh

1 Like

~scanDirectory = { |path, dict|
var d;
if(dict.isNil) {
dict = Dictionary.new;
};
d = Dictionary.new;
// these will be mixed directories or files
(path +/+ “*”).pathMatch.do { |subpath|
switch(File.type(subpath))
{ \regular } {
// all recursion needs a non-recursive, exit branch
// a normal file is the non-recursive branch
~handleOneFile.(subpath, d)
}
{ \directory } {
// a directory is the recursive branch
~scanDirectory.(subpath, d);
}
// maybe need to handle symlinks too,
// but I don’t have time right now
};
// did we find anything?
if(d.notEmpty) {
dict.put(path.basename, d);
};
dict
};

~handleOneFile = { |path, dict|
if(path.splitext[1] == “mp3”) {
dict.put(path.basename, “Buffer goes here I guess”);
};
dict
};
)

Not sure how to adapt to this function. I tried to give the arg path, a PathName which is a folder situated inside my ProjectFolder. My project is stored in the project folder.

´´´
~path = PathName(thisProcess.nowExecutingPath).parentPath; // project path
~buflib = PathName(~path ++ “buflib/”); // soundlibrary path
~scanDirectory.value(~path); // trying to run your function
~scanDirectory.value(~buflib); // trying to run your function
´´´
I quess your solution is completely above my understanding.
When I run d. It is a empty dictionary

Use just a string.

I don’t like the PathName class at all. I guess I should have specified not to use it.

Also note that the example is filtering for mp3s but you can change the condition to match any extensions you like.

hjh

´´´
~path = PathName(thisProcess.nowExecutingPath).parentPath.asString; asString
~buflib = ~path ++ “buflib/”;
~scanDirectory.value(~buflib);
d; //d still return nil
´´´
This is what the post window returns. Not sure that I can acess these by d[\foldername].choose.play
Maybe I am looking the the wrong dictionary

@scnils
Does the following code work for you?
My code is not so elegant as the code by @jamshark70,
but it seems to work well at least on my end:

(
~buffers = Dictionary[];
~pathBase = Platform.resourceDir;
PathName(~pathBase).filesDo { |item| 
	var
	folder = item.folderName.asSymbol,
	path = item.fullPath;

	if ("aiff|aif|wav|flac".matchRegexp(item.extension)) {
		if(~buffers.at(folder).isNil) {
			~buffers.put(folder, []);
			("The dictionary key \\" ++ folder + "is added to ~buffers").postln
		};
		~buffers[folder] = ~buffers.at(folder).add(Buffer.read(s, path));
		("The added file to the key \\" ++ folder + "is" + item.fileName).postln;
	}
}
)

Post <<< ~buffers

~buffers[\sounds]
~buffers[\sounds].size
~buffers[\sounds][0].play
~buffers[\sounds][1].play
~buffers[\sounds][2].play

If the subfolders of your main sound folder are only one level deep, the following code might be applicable even though there is no dictionary:

Alternately, ~path = thisProcess.nowExecutingPath.dirname; ~buflib = ~path +/+ "buflib/";

I’m going to take a guess that the misunderstanding is this:

f = {
	var a;
	a = 2 + 2;
	a
};

f.value;  // 4

a  // nil

Local, declared vars are not the same as global, interpreter variables, even if they share the same name. There is a little bit about this toward the end of the “Functions and Other Functionality” tutorial chapter.

So how do you get the dictionary out of the function?

That’s already done – see the last line of the function. This is the function’s return value. That is dict – the container dictionary in my function.

a = f.value;

a  // now, 4

So…

d = ~scanDirectory.("~/Music".standardizePath);

d  // not nil

The code that I posted should let you access by d["foldername"]["subfoldername"]....

Note, however:

	if(d.notEmpty) {
		dict.put(path.basename, d);
	};

// and

	if(path.splitext[1] == "mp3")

“mp3” was a bad choice but… this means, in the block that I posted, files will be excluded if they don’t match the extension and if a directory doesn’t contain any matching files, then it will not be added into the parent dictionary at all.

This would account for an empty dictionary.

I had suggested that you would want to change the extension condition by yourself. If you didn’t do that, then an empty dictionary would be a likely result. Perhaps:

	if(["wav", "aiff"].includesEqual(path.splitext[1].toLower))

BTW prko’s filesDo solution is a recursive solution! Perfectly fine, except that PathName is not written for efficiency. But maybe efficiency isn’t a high priority in this case.

hjh

1 Like

Yes, this I got to work! Thanks!

1 Like

Sorry. I am missing something here. There definitly now is something in the d directory

~path = thisProcess.nowExecutingPath.dirname; // project path
~buflib = ~path ++ "/buflib/" 


d = ~scanDirectory.(~buflib.standardizePath);
d["buflib"]["cheetahMD16"]["cheetahMD16_bass"].size // is 3 (see below)
d["buflib"]["cheetahMD16"]["cheetahMD16_bass"][0].play  // is nil (see below)

In postwindow:

→ Dictionary[ (buflib → Dictionary[ (brass → Dictionary[ (messain_brass → Dictionary[ (A4_Intermède_BPM87_1_oneshot.wav → Buffer goes here I guess) ]) ]), (cheetahMD16 → Dictionary[ (cheetahMD16_perclayer → Dictionary[ (MD16B041.wav → Buffer goes here I guess), (MD16B055.wav → Buffer goes here I guess), (MD16B023.wav → Buffer goes here I guess), (MD16B047.wav → Buffer goes here I guess), (MD16B015.wav → Buffer goes here I guess),
(MD16B053.wav → Buffer goes here I guess), (MD16B021.wav → Buff…etc…
→ 3
→ nil
´´´

1 Like

This code also worked! But .deepFolders method by @madskjeldgaard need to be added in the PathName.sc.
Sounds is accessed with d[\foldername][0] no matter how deep the folders are nested.

(
var path,samplefolder,allfolders, dictionary;
		d = Dictionary.new;
		path = PathName(thisProcess.nowExecutingPath).parentPath;
		samplefolder = PathName(path ++ "buflib/"); 
        allfolders=samplefolder.deepFolders;
        allfolders.do{ arg n, idx;
	    case{n.files.size > 0} {
		d.add(
		n.folderName.asSymbol ->
		Array.fill(
			n.files.size,
			{
				arg i;
				Buffer.read(s, n.files[i].fullPath);
			}
		);
	);
};
};
)
1 Like

It is excellent that you found several ways!
Where can I find the method?

Here:

Scroll down and add the adjusted code

1 Like

Sure, but did you take a look at what the lowest level dictionary keys are?

I took the rule of indexing all files by their basename.

… meaning, .choose should still work. I had thought that was the desired access method.

Btw if you really want numbers for files, that could be done too: keep a count of the files added and use that as the key when adding a file.

Oh, I also see: Dictionary[ (messain_brass → Dictionary[ (A4_Intermède_BPM87_1_oneshot.wav → Buffer goes here I guess) …

I wasn’t taking responsibility for reading the buffer for you; I figured you could insert that on your own, according to your requirements. The dummy placeholder string is, of course, not going to work for synthesis.

hjh

Yes, sorry. I guess this way of doing it is completely new for me and therefor not really sure about the syntax and understanding where things actually go wrong for me.
Not sure what to put in here “Buffer goes here I guess” because all the files are are itterated with the provided path of the whole sample folder?

Upon a second look, it seems like you want to store the Buffer objects, right?

So you would put the Buffer read instruction here:

~handleOneFile = { |path, dict|
	if(path.splitext[1] == "mp3") {
		dict.put(path.basename, Buffer.read(s, path));
	};
	dict
};

I had thought to give some advice on a code structure that might have helped… and at that time, I was in a bit of a rush so I didn’t fill in every last detail, thinking that you have a better idea what you want than I do, so you could fill in the gaps. In retrospect, this approach appears to have backfired (e.g. edit: even here, I’ve failed to integrate the updated extension condition, so if you paste that version into the example and run it, you might get an empty dictionary again – which points out the issue: at a certain point, it takes rather intensive effort and attention to try to answer a question 100% correctly… even being aware of this, I still overlooked something). So I’d like to suggest that you might use whichever solution in this thread is comfortable for you, and that if you find my approach to be incompatible with your way of thinking, there’s no obligation to pursue it.

At this point, other time demands and lack of energy lead me to withdraw myself from this thread, with apologies for any remaining unanswered questions.

hjh

1 Like

Yes, I did want to store the objects and I wasn’t sure of/aware of the other method of not storing the buffer objects.
I apologize for all the headaches! I definitely understand that it is hard to answer everything super correctly. I am really thankful and eventually I will understand this workflow.
I learned alot and I am really thankful for the help.

1 Like