Function throws error when ran from OSCdef but runs normally when not

I’m currently setting up a cue system using OSC and for some reason when I call my function that chooses the cue to play I get a “message last not understood” on receiver nil error. This error does not happen when the function is called anywhere else, only in the OSCdef. Any insight into why this may be happening would be greatly appreciated. I’m running SC 3.14.1 on Windows on ARM for what it’s worth (possible that it’s an ARM compatibility issue?)

Hi, please provide a minimum code example that triggers the error.

Thanks for the help, below is the minimum I can get it to while throwing the error. EnvEditor is just a class I wrote for editing Envs, for some reason it only throws the error when it is present. I can’t upload attachments so here’s a pastebin link for that code.


	//Loopback
~loop = NetAddr("127.0.0.1", NetAddr.langPort);


	
	//Collection of cues
~mvm1Cues = {
		arg cue;
		var loopback = NetAddr("127.0.0.1", NetAddr.langPort);
		var envelopes = EnvEditor.new(\dummy).init;
		
		
		switch(cue,
		\test, {'test'.postln;}

	);
	};
	
	
	~cueControl = {arg mvmt, cue;
		switch(mvmt,
		\mvm1, {
				~mvm1Cues.value(cue, 0, 0, 0);
			}
		
		);
	};
	
		
	//OSCdef 
	OSCdef(\cue, {
		|msg|
		~cueControl.value(msg.at(1), msg.at(2));
	}, \cue, recvPort: NetAddr.langPort);
	
		~cueControl.value(\mvm1, \test); //works
	
	~loop.sendMsg(\cue, \mvm1, \test); //error
EnvEditor {
	var id;
	var <min;
	var <max;
	var <>env;
	var <>editorGUI;
	var <envDict;
	classvar pathId;


	*new {
	|envelopeId, min = 0, max = 1|
		pathId = PathName(thisProcess.nowExecutingPath).parentPath +/+ "/savedEnvs.scd";

		^super.newCopyArgs(envelopeId, min, max);
	}

	init {
		this.loadEnv;
		// this.rescale(min, max);
		^this;
	}

	edit {
		var editor = EnvEditorGUI.new(this);
		editor.open;
		// this.editorGUI.convertXY;


	}

	playEnv {
	| synth, control, duration, envelope, pollingRate = 0.125|
		var envStream;
		if(envelope == nil, {envelope = env},{
			envelope = this.getEnv(envelope);
		}
		);
		envelope.times = envelope.times.normalizeSum * duration;
		{
		envStream = envelope.asStream;
		(1/pollingRate * envelope.times.sum + 1).do({
				synth.set(control, envStream.next);
				pollingRate.wait;
		})
		}.fork

	}



	rescale {
		|newMin, newMax|

		min = newMin;
		max = newMax;
		env.levels = env.levels.linlin(env.levels.minItem, env.levels.maxItem, min, max);

	}


	saveEnv {

		if(File.exists(pathId), {
			var dict = pathId.load;
			if(dict.includesKey(id),{
			dict[id] = env;
			},{
				dict.put(id, env);
			});

			File.use(pathId, "w", {|f| f.write(dict.asCompileString)});


		},{
			var file = File.new(pathId, "w");
			var dict =Dictionary.new();

			dict.put(id, env);


			File.use(pathId, "w", {|f| f.write(dict.asCompileString)});

		};
		);
	}

	loadEnv {

			if(File.exists(pathId), {

				var dict = pathId.load;
			envDict = dict;

			if(dict.includesKey(id), {
				env = dict[id];
			},{env = Env.new(curve: [0, 0])});
		},{env = Env.new(curve: [0, 0])});

	}

	getEnv {
		|envid|
		if(envDict.includesKey(envid), {
		^envDict[envid];
		});

	}
}


EnvEditorGUI {
	var parent;
	var win;
	var <>heldPoint;
	var <>holding;
	var heldLine;
	var holdingLine;
	var totalTime;
	var normLevels;
	var points;
	var curves;

	*new {
	|editor|

		^super.newCopyArgs(editor);

	}

	open {
		var updater;
		var convertTimes = Array(64);
		holding = false;
		holdingLine = false;
		parent.editorGUI = this;

		win = Window.new("EnvEditor", Rect(500, 600, 510, 210)).front.alwaysOnTop_(true);

		normLevels = parent.env.levels.linlin(parent.min, parent.max, 200, 0);

		points = Array(64);
		totalTime = parent.env.times.sum;
		convertTimes.insert(0, 0);


		parent.env.times.do({
			|i, j|

			convertTimes.insert(j+1, convertTimes[j] + i);
		});

		convertTimes = convertTimes.normalize(0,1) * 500;


		normLevels.do({
			|i, j|
			points.insert(j, Point(convertTimes[j] ,i))
		});



		Button.new(win, Rect(0,0,10,10)).action_({
			parent.saveEnv;

			});



		win.view.mouseDownAction_({
			|view, x, y, modifiers|

			switch(modifiers)
			{262144} {"control".postln; this.addPoint(x, y);} //Adds new point
			{131072} {"shift".postln}
			{524288} {"alt".postln; holdingLine = true;
				(points.size - 1).do({
					|i|
					if(
						(points[i].x < x) &&
						(points[i+1].x > x), {
							holdingLine = true;
							heldLine = i;
							i.postln;
						};
					);
				});
			}
			{786432} {"control+alt".postln;
				points.do({
					|i, j|


					if(
						((i.x - 5) < x) &&
						((i.x + 5) > x) &&
						((i.y - 5) < y) &&
						((i.y + 5) > y), {
							points.removeAt(j);
							parent.env.curves.removeAt(j);


							});

					});

				} // Deletes point
			{655360} {"alt+shift".postln}
			{393216} {"control+shift".postln}
			{917504} {"control+shift+alt".postln};

			points.do({
				|i, j|
				i.postln;


				if(
					((i.x - 5 + 5) < x) &&
					((i.x + 5 + 5) > x) &&
					((i.y - 5 + 5) < y) &&
					((i.y + 5 + 5) > y), {
						heldPoint = j;
						holding = true;



						});

				});

			});



		win.view.mouseUpAction_({holding = false; heldPoint = nil; holdingLine = false; heldLine = nil; this.convertXY;});

		win.view.mouseMoveAction_({
			|view, x, y, modifiers|

			if(holding, {


				switch(heldPoint)
				{0} {
					if((y > 0) && (y < 200),{
					points[heldPoint].y = y;
					});

				}
				{points.size - 1} {
					if((y > 0) && (y < 200),{
					points[heldPoint].y = y;
					});
				}
				{
					if(
						(x > 5) &&
						(x < 495) &&
						(y > 5) &&
						(y < 205), {
					points[heldPoint].x = x - 5; points[heldPoint].y = y - 5;
					});

				}

				});

			if(holdingLine && (modifiers == 524288), {
				var newVal = ((y / 200) * 40 - 20).clip(-20, 20);
				newVal.postln;

				parent.env.curves[heldLine] = newVal;


			});



			});

		win.drawFunc_({
			this.update;

			});

		updater = Task({
			loop {
				{win.refresh}.defer;
				(1/30).wait;
				}
			}).play(AppClock);

		win.onClose_({updater.stop;});

		this.convertXY;

	}

	update {

		2.do({
			Pen.line(Point(5, 5), Point(505, 5));
			Pen.stroke;
			Pen.line(Point(5, 205), Point(505, 205));
			Pen.stroke;

		});
		Pen.color_(Color.gray);

		5.do({
			|i|
			Pen.moveTo(Point(i * 125 + 5, 5));
			Pen.line(Point(i * 125 + 5, 5), Point(i * 125 + 5, 205));
			Pen.stroke;

		});

		Pen.color_(Color.black);


		if(points.size > 1, {
			parent.env.curves.size.do({
			|i|
				var midpoint = Point(points[i].x + points[i+1].x / 2, points[i].y + points[i+1].y / 2);
				var curveVal;
				var curveEndPos;
				var curveEndNeg;
				curveVal = parent.env.curves[i];
				curveEndNeg = Point(points[i].x, points[i+1].y);
				curveEndPos = Point(points[i+1].x, points[i].y);



				Pen.moveTo(points[i] + Point(5,5));

				if(points[i].y > points[i+1].y, {curveVal},{curveVal = curveVal * -1});
				Pen.quadCurveTo(
					points[i+1] + Point(5,5),
					Point(5,5) + Point(curveVal.linlin(-20, 20, curveEndNeg.x, curveEndPos.x), curveVal.linlin(-20, 20, curveEndNeg.y, curveEndPos.y)));
			Pen.stroke;

		});

		});

		if(points.size > 0, {
			points.do({
				|i, j|


				Pen.circle(Rect(i.x -4 + 5, i.y -4 + 5, 8, 8));
				Pen.stroke;
			});
		});
	}

	addPoint {
		|x, y|
		(points.size - 1).do({
			|i|


			if(
				(points[i].x < x) &&
				(points[i+1].x > x),
				{
					points.insert(i+1, Point(x, y));
					parent.env.curves = parent.env.curves.insert(i+1, 0);

			});
		});

		this.convertXY;
	}


	convertXY {
		var newTimes = Array.new(64);
		var newLevels = Array.new(64);
		var newCurves = Array.new(64);


		points.do({
			|i, j|

			newLevels.insert(j, 200 - i.y);
		});
		(points.size - 1).do({
			|i|


			newTimes.insert(i,  (points[i+1].x/500 * totalTime) - (points[i].x/500 * totalTime));
		});
		// newLevels.insert(newLevels.size + 1, 500 - points[points.size-1].y);
		parent.env.levels.postln;
		parent.env.levels = newLevels.linlin(0, 200, parent.min, parent.max);
		parent.env.levels.postln;
		parent.env.times = newTimes;
	}

}

The problem is that inside a OSC func thisProcess.nowExecutingPath returns nil. Don’t ask me why…

Just call thisProcess.nowExecutingPath once when you setup your project and store the result in some variable.

(Note that thisProcess.nowExecutingPath also returns nil if you call it in a new document that hasn’t been saved yet. Better always do a nil-check.)

1 Like

Ah that checks out, thank you so much for the help! Storing an instance of EnvEditor in a variable makes more sense anyway. Many thanks

The inconsistency here is not about OSC funcs vs any other funcs – Functions don’t store the path in which they were defined, so, they don’t know. The inconsistency is with functions vs routines – a Routine does remember the file that was active when it was created(+). Agreed that it would be better if functions did as well (there’s a PR about that… oh… waiting for my review :flushed: ).

@drewmfarrar I think, if it were my EnvEditor class, I would define the new method to receive the path from the user as an argument. Then you could have another convenience method to supply the executing path automatically. Also, why is pathId a class variable? Maybe you would want multiple EnvEditors open at the same time looking at different savedEnv files.

EnvEditor {
	var id;
	var <min;
	var <max;
	var <pathId;
	var <>env;
	var <>editorGUI;
	var <envDict;
	
	*new { |envelopeId, min = 0, max = 1, path|
		if(path.notNil) {
			path = thisProcess.nowExecutingPath.dirname +/+ "/savedEnvs.scd";
		};
		
		^super.newCopyArgs(envelopeId, min, max, path);
	}
	
	*newRelative { |envelopeId, min = 0, max = 1|
		^this.new(envelopeId, min, max, thisProcess.nowExecutingPath)
	}
	...
}

Then you would have both options: explicitly set the path, or automatically grab it.

hjh

1 Like

Ah, I was wondering why it does work with Routines! Thanks for clearing that up!

Thanks for the suggestion! It’s been awhile since I’ve written a class in supercollider so I’m knocking the rust back off (and making some mistakes like misremembering how var and classvar work…)

When the lexer pr gets merged, I’ll need to fix this to get the error pr working nicely. You’ll be able to do thisFunctionDef.path or something similar. Could even introduce a new (pseudo)keyword for that, say thisPath, if it becomes desirable.

2 Likes

Just checking, is Move filenameSym from Method to FunctionDef by JordanHendersonMusic · Pull Request #6678 · supercollider/supercollider · GitHub part of that initiative or superseded by it?

hjh

Superceded. The way the lexer & parser passed this information around was awkward. It would also be nice if you can set this manually.

Ok, I’ll close it then (if you didn’t get to it first).

hjh